Java非阻塞IO选择器导致通道寄存器阻塞
我有两个线程正在处理JavaNIO的非阻塞套接字。这是线程正在执行的操作: 线程1: 调用选择器的select()方法的循环。如果有任何键可用,则会相应地对其进行处理 线程2: 偶尔通过调用register()将SocketChannel注册到选择器 问题是,除非select()的超时非常小(大约100ms),否则对register()的调用将无限期地阻塞。即使通道被配置为非阻塞,并且javadocs声明选择器对象是线程安全的(但我知道它的选择键不是) 有人对这个问题有什么想法吗?如果我将所有内容都放在一个线程中,那么应用程序就可以完美地工作。没有问题发生,但我真的希望有单独的线程。感谢您的帮助。我在下面发布了我的示例代码: 将select(1000)更改为select(100),它将工作。将其保留为select()或select(1000),则不会 } } 捕获(IOE异常) { 系统错误println(e); }Java非阻塞IO选择器导致通道寄存器阻塞,java,sockets,nonblocking,Java,Sockets,Nonblocking,我有两个线程正在处理JavaNIO的非阻塞套接字。这是线程正在执行的操作: 线程1: 调用选择器的select()方法的循环。如果有任何键可用,则会相应地对其进行处理 线程2: 偶尔通过调用register()将SocketChannel注册到选择器 问题是,除非select()的超时非常小(大约100ms),否则对register()的调用将无限期地阻塞。即使通道被配置为非阻塞,并且javadocs声明选择器对象是线程安全的(但我知道它的选择键不是) 有人对这个问题有什么想法吗?如果我将所有内
} 公共UDPSocket(字符串DSTOST,int dstPort) { 尝试 { this.dstHost=dstHost; this.dstPort=dstPort; clientChannel=DatagramChannel.open(); clientChannel.configureBlocking(false); } 捕获(IOE异常) { 系统错误println(e); } } public void addListener(SocketSubscriber订户) { 尝试 { DatagramChannel服务器通道=DatagramChannel.open(); serverChannel.configureBlocking(false); DatagramSocket socket=serverChannel.socket(); 绑定(新的InetSocketAddress(dstPort)); SelectionKey=serverChannel.register(recvSelector,SelectionKey.OP_READ); 密钥。附加(订户); } 捕获(IOE异常) { 系统错误println(e); } } 公共无效发送(ByteBuffer缓冲区) { 尝试 { send(缓冲区,新的InetSocketAddress(dstHost,dstPort)); } 捕获(IOE异常) { 系统错误println(e); } } 公众假期结束() { 尝试 { clientChannel.close(); } 捕获(IOE异常) { 系统错误println(e); } } }
导入java.nio.ByteBuffer
公共接口SocketSubscriber
{
公共数据无效(ByteBuffer数据);
}
用法示例:
Object registeringSync = new Object();
公共类测试实现SocketSubscriber
{
公共静态void main(字符串[]args)引发异常
{
UDPSocket.init();
UDPSocket测试=新的UDPSocket(“本地主机”,1234);
addListener(newtest());
udpsockettest2=新的UDPSocket(“localhost”,4321);
test2.addListener(newtest());
System.out.println(“侦听…”);
ByteBuffer缓冲区=ByteBuffer.allocate(500);
发送(缓冲区);
buffer.rewind();
test2.send(缓冲区);
System.out.println(“数据发送…”);
睡眠(5000);
UDPSocket.shutdown();
}
@凌驾
公共无效数据(ByteBuffer数据)
{
System.out.println(“接收”+数据.limit()+“数据字节”);
}
}
选择器具有多个记录在案的内部同步级别,您将遇到所有这些级别。在调用register()之前,先在选择器上调用wakeup()
。
如果选择的键为零,请确保select()
循环正常工作,这就是wakeup()上会发生的情况。
我今天遇到了相同的问题(即“wakeupAndRegister”不可用)。我希望我的解决方案可能会有所帮助:
创建同步对象:
synchronized (registeringSync) {
selector.wakeup(); // Wakes up a CURRENT or (important) NEXT select
// !!! Might run into a deadlock "between" these lines if not using the lock !!!
// To force it, insert Thread.sleep(1000); here
channel.register(selector, ...);
}
通过执行以下操作注册频道:
public void run() {
while (initialized) {
if (selector.select() != 0) { // Blocks until "wakeup"
// Iterate through selected keys
}
synchronized (registeringSync) { } // Cannot continue until "register" is complete
}
}
线程应执行以下操作:
我遇到了与海报相同的问题,但我对这个解决方案并不完全满意。由于线程调度,在寄存器发生之前,没有任何东西阻止唤醒和重新选择。@Davidermann如果您可以控制调用
select()
的线程的代码,则可以实现某种控制以防止立即重新选择,例如if(select()==0)sleep(1)
可能会产生足够的唤醒();寄存器()要在下一次select()
发生之前执行的代码>。解决方案仍然有效(+1),问题来自一个“缺少的方法”wakeupAndRegister()
,该方法将以原子方式执行。@davidermann我对整个设计不满意。为什么要指定三个级别的同步,并将其硬连接到抽象基类中,这对我来说是一个谜,因为除了这个奇怪的wakeup()之外,从一个单独线程注册的机制几乎完全留给了我们
method.@davidermann有关您的问题,请参见。如果问题可能导致死锁,那么执行“睡眠(1)”之类的操作是一个非常糟糕的主意。多线程不能做“好”或“坏”。。。不管“对”与否。不管怎样,每次出现“睡眠”都可能是不好的。已经有三个级别的同步,这就是问题所在。添加第四个并不能解决任何问题,它解决了。如果不进行额外的锁定,如果线程进入一个新的select“BEVER”wakeup和register(在那里添加了注释),代码可能会陷入死锁。没有死锁。死锁要求两个线程都被阻塞,等待对方的锁。不要误用标准术语。标准术语可能重复
Object registeringSync = new Object();
synchronized (registeringSync) {
selector.wakeup(); // Wakes up a CURRENT or (important) NEXT select
// !!! Might run into a deadlock "between" these lines if not using the lock !!!
// To force it, insert Thread.sleep(1000); here
channel.register(selector, ...);
}
public void run() {
while (initialized) {
if (selector.select() != 0) { // Blocks until "wakeup"
// Iterate through selected keys
}
synchronized (registeringSync) { } // Cannot continue until "register" is complete
}
}