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

rih*_*iha 10 java multithreading nio

我有一个Thread私有Selector和公共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<SelectionKey> 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()) {
          ...
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

问题: 这是一种"好"的方式,还是有任何严重的缺点?它会更好使用列表或队列(如建议在这里)来存储频道注册,或更复杂的锁像这样呢?它的优点/缺点是什么?还是有"更好"的方式?

irr*_*ble 5

正如 Darron 建议的那样,只需将 Selector 等视为非线程安全的,在同一线程上执行所有与选择相关的操作。

NIO选择器的并发模型就是废话。我必须指出这一点,因为这对于每个尝试研究它的人来说都是巨大的时间浪费。最后的结论是,算了,不是并发用的。


Gre*_*lli 3

实际上,我感到惊讶的是,在编译时没有删除空块的锁获取。很酷,它有效。我的意思是它有效,它是先发制人的,这不是最漂亮的方法,但它有效。它比睡眠更好,因为它是可预测的,并且由于您使用唤醒呼叫,您知道将根据需要取得进展,而不是定期更新(如果您纯粹依赖于选择超时)。

这种方法的主要缺点是,您说注册调用胜过其他任何事情,甚至服务请求。这在您的系统中可能是正确的,但通常情况并非如此,我想说这是一个可能的问题。一个更具前瞻性的小问题是,您锁定了 SelectorThread 本身,在本例中它是一个更大的对象。不错,但随着您的扩展,这个锁也不是很好,只要其他客户端使用此类,就只需记录并考虑该锁。就我个人而言,我会完全制作另一把锁,以避免未来出现任何不可预见的危险。

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

虽然我承认,自从我上次写这些东西以来已经有很长一段时间了,还有其他库可以为你处理排队问题。

Grizzly Nio框架虽然有点旧,但我上次使用它时,主runloop还不错。它为你安排了很多排队。

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

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

  • 这是一个单人项目,只是为了玩弄框架吗?
  • 这是一段你想使用多年的生产代码吗?
  • 这是您正在迭代的一段生产代码吗?

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