NIO选择器:如何在选择时正确注册新通道

我有一个带有私有Selector的子类Thread和一个公共register(SelectableChannel channel, ...)方法,它允许其他线程将通道注册到选择器。

如上所述,通道的register()在选择器的select() / select(long timeout)期间阻塞,因此我们需要wakeup()选择器。

我的线程无限期地选择(除非它被中断)并且它实际上设法在调用通道的register()之前进入下一个选择。 所以我认为我使用带有synchronized块的简单锁来确保register()首先发生。

代码:(为了便于阅读,删除了不相关的代码)

 public class SelectorThread extends Thread { ... public void register(SelectableChannel channel, Attachment attachment) throws IOException { channel.configureBlocking(false); synchronized (this) { // LOCKING OCCURS HERE selector.wakeup(); channel.register(selector, SelectionKey.OP_READ, attachment); } } @Override public void run() { int ready; Set readyKeys; while (!isInterrupted()) { synchronized (this) {} // LOCKING OCCURS HERE try { ready = selector.select(5000); } catch (IOException e) { e.printStackTrace(); continue; } if (ready == 0) { continue; } readyKeys = selector.selectedKeys(); for (SelectionKey key : readyKeys) { readyKeys.remove(key); if (!key.isValid()) { continue; } if (key.isReadable()) { ... } } } } } 

这个简单的锁允许register()在线程继续下一个选择循环之前发生。 据我测试,这是假设的。

问题:这是一种“好”的方式,还是有任何严重的缺点? 使用列表或队列(如此处所示 )存储注册频道或更复杂的锁定会更好吗? 它的优点/缺点是什么? 还是有“更好”的方式?

我真的很惊讶在编译时没有删除空块的锁获取。 非常酷,它的工作原理。 我的意思是它有效,它是先发制人的,它不是最漂亮的方法,但它有效。 它比睡眠更好,因为它是可预测的,因为你使用唤醒呼叫,你知道如果你纯粹依赖于选择超时,将根据需要而不是定期更新进行。

这种方法的主要缺点是,你说要调用注册特朗普,甚至服务请求。 在您的系统中可能是这样,但通常情况并非如此,我想这可能是一个问题。 一个更具前瞻性的小问题是你锁定了SelectorThread本身,在这种情况下它是一个更大的对象。 不错,虽然扩展时并不好,但只要其他客户使用此类,此锁就只需记录并考虑在内。 就个人而言,我会完全制造另一个锁,以避免任何不可预见的未来危险。

就个人而言,我喜欢排队技巧。 他们通过这种方式为您的线程分配角色,如主人和工人。 虽然所有类型的控制都发生在主服务器上,比如每次选择检查队列中的更多注册后,清除并清除任何读取任务,处理整个连接设置中的任何更改(断开连接等)…“bs”并发模型似乎很好地接受这个模型,它是一个非常标准的模型。 我不认为这是一件坏事,因为它使代码变得不那么hacky,更容易测试,更容易阅读imo。 只需花一点时间写出来。

虽然我承认,自从我上次写这篇文章已经有很长一段时间了,还有其他的图书馆可以为你排队。

Grizzly Nio Framework有点老了,上次我用它时,主要的runloop也不错。 它为你设置了很多排队。

Apache Mina类似于它提供了一个排队框架。

但我的意思是最终取决于你在做什么。

  • 它是一个单独的项目,只是为了玩框架?
  • 它是你想要生活多年的生产代码吗?
  • 它是你正在迭代的生产代码吗?

除非您计划将此作为您向客户提供的服务的核心部分,否则我会说您的方法很好。 从长远来看,它可能只有维护问题。

只是将Selector等视为不是线程安全的,在Darron建议的同一线程上执行所有选择相关的操作。

NIO选择器的并发模型是废话 。 我必须把它叫出来,因为对于每个试图研究它的人来说,这是一个巨大的浪费时间。 最后,结论是,忘记它,它不是同时使用。

你需要的只是在register()之前的wakeup(),并且在select循环中,如果’ready’为零,则在继续之前进行短暂的睡眠,以使register()有机会运行。 没有额外的同步:它已经够糟糕了; 不要让它变得更糟。 我不喜欢注册,取消,改变兴趣等等的这些队列:他们只是顺序化了可以真正并行完成的事情。