Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/305.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
调用select()时,Java线程在向选择器注册通道时阻塞。怎么办?_Java_Multithreading_Deadlock_Nio - Fatal编程技术网

调用select()时,Java线程在向选择器注册通道时阻塞。怎么办?

调用select()时,Java线程在向选择器注册通道时阻塞。怎么办?,java,multithreading,deadlock,nio,Java,Multithreading,Deadlock,Nio,我有一个基本问题。SelectableChannel的register方法在阻塞调用中的原因和方式。 让我提供一个场景 我在类寄存器中创建了一个选择器对象,如下所示 private static Selector selector = Selector.open(); 我在同一个类(Register)中也有一个方法,用选择器注册通道 public static SelectionKey registerChannel(SelectableChannel channel, int ops)

我有一个基本问题。SelectableChannel的register方法在阻塞调用中的原因和方式。 让我提供一个场景

我在类寄存器中创建了一个选择器对象,如下所示

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方法是从同一个线程调用的,那么它就能够注册,但是在这之后,如果其他线程尝试调用该方法,那么线程就不会前进。

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


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

您需要使用锁并手动同步

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

final ReentrantLock selectorLock = new ReentrantLock();
然后,当您需要向选择器注册时,执行以下操作:

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

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

   ....
}
最后,在调用accept()的循环过程中,如下所示:

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

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

   ....
}
然后继续你剩下的逻辑


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

这是大多数NIO实现的基本特性,从文档中看不明显


您需要从正在进行选择的同一线程进行所有注册调用,否则会发生死锁。通常情况下,这是通过提供一个注册/注销/兴趣更改队列来完成的,该队列将被写入,然后是selector.wakeup()调用。当选择线程唤醒时,它将检查队列并执行任何请求的操作。

从任何线程注册频道:

synchronized (selectorLock2) {
   selector.wakeup();
   synchronized (selectorLock1) {
       channel.register(selector, ops);
   }
}
选择器循环应如下所示:

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

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

   ....
}

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

基本上,需要做的是:

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
}

这是一个代码示例。

+1。根据经验,我支持这一点。做得很好。这会给每个注册增加500毫秒不必要的延迟。请改用中的方法。@DavidB。它不会给每个注册增加任何延迟。阻止500毫秒的是选择线程,而不是注册线程,以及选择线程不管怎样,d总是想在
select()
中阻塞。答案中的声明大部分是不正确的。我在实现这一点时遇到了一个问题。开始时,除了
selector.select(500)
语句之外,在
selectorLock.lock()
selectorLock.unlock()之间没有其他任务
在选择器while循环中,因为还没有注册通道。循环转得太快,以至于另一个线程很难干预并获取
selectorLock
(需要30秒到分钟)。如果我在while循环中但在
selectorLock.lock()和
selectorLock.unlock()之外向控制台打印内容
,其他线程有机会获得selectorLock。如果循环中没有任务,它可能需要睡眠几毫秒。反对:仍然存在竞争条件。假设
select
线程通过锁定/解锁行,然后暂停。另一个线程开始
寄存器
块,完成
唤醒
行,然后暂停。
select
线程继续,并开始调用
select
。这是结束语句的反例。我对导致死锁的确切条件有点模糊,但某种形式的“
select
register
同时发生”在给定的场景中,可以从这一点访问的。超时可能会阻止完全死锁,但仍然会浪费时间。这是所有NIO实现的基本功能,完全在Javadoc中指定。在
select()、
register()中有三个嵌套同步
尝试其中一个。结果不是死锁,而是一个块,它只会持续到并发的'select()'调用返回为止。解决方案是在
register()之前调用
wakeup()
强制
select()
取消阻塞并返回零,这将释放其三个锁,从而允许
register()
声明所需的解决方案并继续。上述注释中提供的解决方案是错误的–至少给出了解释。根据线程的调度,选择线程可能会完成整个循环并在注册线程能够调用register()之前重新输入select()当前的Java8Javadoc只在S中提到这个问题