16

我有一个基本问题。为什么以及如何 SelectableChannel 的 register 方法可以阻塞调用。让我提供一个场景。

我在类 Register 中创建了一个 Selector 对象,如下所示。

private static Selector selector = Selector.open();

我在同一个类(Register)中也有一个方法来向选择器注册通道。

public static SelectionKey registerChannel(SelectableChannel channel, int ops)
                             throws IOException {
   channel.configureBlocking(false);
   return channel.register(selector, ops);
}

还有一个名为 Request 的类,它具有从通道读取数据、处理并调用以下方法来注册通道的方法。

selectonKey = Register.register(socketChannel, SelectionKey.OP_READ);

此时线程被阻塞,没有给出它在等待什么的线索。我已验证选择器已打开。请为我提供一些帮助以了解如何解决此问题。有没有我可以释放的锁。

任何输入将不胜感激。

添加到我所描述的内容。进一步的测试表明,如果从同一个线程调用 Register.register 方法,它可以注册,但之后如果其他线程尝试调用该方法,则线程不会继续前进。

4

5 回答 5

38

这是大多数 NIO 实现的基本功能,在文档中并不明显。

您需要从进行选择的同一线程进行所有注册调用,否则将发生死锁。通常这是通过提供一个注册/取消注册/兴趣更改队列来完成的,该队列被写入,然后调用 selector.wakeup()。When the selecting thread wakes up it checks the queue and performs any requested operations.

于 2010-02-01T19:55:14.803 回答
12

您需要使用锁并手动同步。

在您运行选择器循环的同一线程中,有一个 ReentrantLock:

final ReentrantLock selectorLock = new ReentrantLock();

然后,当您需要向选择器注册时,请执行以下操作:

selectorLock.lock();
try {
    selector.wakeup();
    socketChannel.register(selector, ops);
} finally {
    selectorLock.unlock();
}

最后,在你调用accept()的循环中,像这样:

selectorLock.lock();
selectorLock.unlock();

selector.select(500);

然后继续你的其余逻辑。

这个构造通过确保对应的和调用之间register()永远不会有另一个来保证调用不会阻塞。select()wakeup()register()

于 2009-07-11T02:55:59.743 回答
1

我同意@Darron 的回答,即您应该将register调用传递给选择器线程,但您不应该使用selector.wakeup它,因为它会引入竞争条件(想象选择器线程正忙于处理其他注册并且您wakeup无法唤醒任何人)。幸运的是,Java NIO 提供了Pipe让您可以让选择器同时监听register调用和其他事件。

基本上,这是需要做的事情:

val registrationPipe = Pipe.open()
registrationPipe.source().configureBlocking(false)
registrationPipe.source().register(selector, SelectionKey.OP_READ)
// now start your selector thread

// now to register a call from other threads using message pleaseRegisterMe
registrationPipe.sink().write(pleaseRegisterMe)

// inside your selector thread
val selectionKey = iterator.next()
if (selectionKey.channel() === registrationPipe.source()) {
    registrationPipe.source().read(pleaseRegisterMe)
    // do something with the message pleaseRegisterMe and do the actual register
}

这是一个完整的工作示例

于 2019-01-30T09:28:45.557 回答
0

您是否尝试过打印程序中所有线程的堆栈跟踪(kill -QUIT在 Unix 中使用或在 Windows 中使用 Ctrl+Break 或使用jstack实用程序)?

AbstractSelectableChannel包含一个需要同步configureBlocking的锁。register该锁也可以通过该blockingLock()方法访问,因此另一个线程可能会持有该锁,从而导致您的寄存器调用无限期地阻塞(但没有堆栈跟踪很难判断)。

于 2009-06-29T09:30:59.393 回答
0

从任何线程注册您的频道:

synchronized (selectorLock2) {
   selector.wakeup();
   synchronized (selectorLock1) {
       channel.register(selector, ops);
   }
}

您的选择器循环应如下所示:

while (true) {
   synchronized (selectorLock1) {
       selector.select();
   }
   synchronized (selectorLock2) {}

   ....
}
于 2014-02-26T10:50:16.447 回答