Java NIO选择器:如何在选择时正确注册新通道
我有一个子类Java NIO选择器:如何在选择时正确注册新通道,java,multithreading,nio,Java,Multithreading,Nio,我有一个子类线程,带有一个私有的选择器和一个公共的寄存器(SelectableChannel,…)方法,该方法允许其他线程向选择器注册通道 正如回答的那样,频道的寄存器()在选择器的选择()/选择(长超时)期间阻塞,因此我们需要唤醒()选择器 我的线程无限期地选择(除非它被中断),并且在调用通道的register()之前,它实际上设法进入下一个选择。所以我想我使用一个带有synchronized块的简单锁来确保register()首先发生 代码:(为了可读性,删除了不相关的代码) 公共类选择器读
线程
,带有一个私有的选择器
和一个公共的寄存器(SelectableChannel,…)
方法,该方法允许其他线程向选择器注册通道
正如回答的那样,频道的寄存器()
在选择器的选择()
/选择(长超时)
期间阻塞,因此我们需要唤醒()
选择器
我的线程无限期地选择(除非它被中断),并且在调用通道的register()
之前,它实际上设法进入下一个选择。所以我想我使用一个带有synchronized
块的简单锁来确保register()
首先发生
代码:(为了可读性,删除了不相关的代码)
公共类选择器读取扩展线程{
...
公共无效寄存器(SelectableChannel,附件)引发IOException{
信道配置阻塞(假);
同步(此){//锁定发生在此处
selector.wakeup();
通道寄存器(选择器,
选择key.OP_读取,
附件);
}
}
@凌驾
公开募捐{
int就绪;
设置readyKeys;
而(!isInterrupted()){
同步(此){}//锁定发生在此处
试一试{
就绪=选择器。选择(5000);
}捕获(IOE异常){
e、 printStackTrace();
继续;
}
如果(就绪==0){
继续;
}
readyKeys=selector.selectedKeys();
用于(选择键:readyKeys){
readyKeys.移除(键);
如果(!key.isValid()){
继续;
}
if(key.isReadable()){
...
}
}
}
}
}
这个简单的锁允许register()
在线程继续执行下一个select循环之前发生。据我测试,这是正常的
问题:
这是一个“好”的方法,还是有任何严重的缺点?使用一个列表或队列(如建议的)来存储频道以进行注册,还是使用更复杂的锁来代替?这样做的利/弊是什么?或者有什么“更好”的方法吗?只需将选择器等视为不安全的线程,按照Darron的建议,在同一线程上执行所有与选择相关的操作
NIO选择器的并发模型是胡说八道。我必须大声说出来,因为对每个试图研究它的人来说,这是一个巨大的时间浪费。最后,结论是,忘了它吧,它不是并发使用的。您所需要的只是在
寄存器()之前进行唤醒()
,并且在select循环中,如果'ready'为零,则在继续之前进行短暂的睡眠,以便给寄存器()
一个运行的机会。没有额外的同步:已经够糟糕了;别让事情变得更糟。我不喜欢这些要注册、取消、更改兴趣操作等的队列:它们只是将真正可以并行完成的事情顺序化。我真的很惊讶在编译时没有删除空块的锁获取。这真是太酷了。我的意思是它是有效的,它是先发制人的,它不是最漂亮的方法,但它是有效的。它比睡眠好,因为它是可预测的,而且由于您使用唤醒呼叫,因此您知道如果您纯粹依赖选择超时,将根据需要进行进度,而不是定期更新
这种方法的主要缺点是,您说注册调用胜过任何其他调用,甚至服务请求。这在你的系统中可能是正确的,但通常不是这样,我认为这是一个可能的问题。更具前瞻性的一个小问题是,您锁定了SelectorRead本身,在本例中,它是一个更大的对象。不错,虽然不是很好,但随着您的扩展,只要在其他客户机使用该类时记录并考虑这个锁就可以了。就我个人而言,我会做另一个锁,以避免任何不可预见的未来的危险
就我个人而言,我喜欢排队技术。它们将角色分配给线程,就像主线程和工作线程一样。然而,所有类型的控制都发生在主机上,比如在每次选择检查队列中的更多注册后,清除并转出所有读取任务,处理整个连接设置中的任何更改(断开连接等)。。。“bs”并发模型似乎很好地接受了这个模型,它是一个非常标准的模型。我不认为这是一件坏事,因为它使代码不那么粗糙,更易于测试,更易于阅读。只是需要更多的时间来编写
虽然我承认,自从我上次写这篇文章已经有很长一段时间了,但还有其他的库在那个里为你们排队
虽然有点旧,但上次我使用它时,主运行循环还不错。它为你们设置了很多排队
类似之处在于它提供了一个排队框架
但我的意思是,最终这取决于你在做什么
- 这是一个一人的项目,只是为了玩弄这个框架吗
- 这是一段你想延续多年的生产代码吗
- 您正在迭代的是一段生产代码吗
除非您计划将此作为您向客户提供的服务的核心部分,否则我认为您的方法很好。从长远来看,它可能只是有维护问题。一种可能的方法是将通道注册(或需要在NIO循环中完成的其他外部任务)注入到选择循环,如下所示
//private final Set<ExternalEvent> externalTaskEvents = ConcurrentHashMap.newKeySet();
//...
while (!Thread.currentThread().isInterrupted()) {
try {
selector.select();
} catch (IOException ex) {
ex.printStackTrace(Log.logWriter);
return;
}
//handle external task events
Iterator<ExternalEvent> eitr = externalTaskEvents.iterator();
while (eitr.hasNext()) {
ExternalEvent event = eitr.next();
eitr.remove();
if(event.task != null){
event.task.accept(event);
}
}
//handle NIO network events
Iterator<SelectionKey> nitr = selector.selectedKeys().iterator();
while (nitr.hasNext()) {
SelectionKey key = nitr.next();
nitr.remove();
if (!key.isValid()) {
continue;
}
try {
if (key.isAcceptable()) {
onAcceptable(key);
} else if (key.isConnectable()) {
onConnectable(key);
} else {
if (key.isReadable()) {
onReadable(key);
}
if (key.isWritable()) {
onWritable(key);
}
}
} catch (IOException | InterruptedException | CancelledKeyException ex) {
ex.printStackTrace(Log.logWriter);
//...
}
}
}
//私有最终集externalTaskEvents=ConcurrentHashMap.newKeySet();
//...
而(!Thread.currentThread().isInterrupted()){
试一试{
selector.select();
}捕获(IOEX异常){
例如printStackTrace(Log.logWriter);
返回;
}
//手
//private final Set<ExternalEvent> externalTaskEvents = ConcurrentHashMap.newKeySet();
//...
while (!Thread.currentThread().isInterrupted()) {
try {
selector.select();
} catch (IOException ex) {
ex.printStackTrace(Log.logWriter);
return;
}
//handle external task events
Iterator<ExternalEvent> eitr = externalTaskEvents.iterator();
while (eitr.hasNext()) {
ExternalEvent event = eitr.next();
eitr.remove();
if(event.task != null){
event.task.accept(event);
}
}
//handle NIO network events
Iterator<SelectionKey> nitr = selector.selectedKeys().iterator();
while (nitr.hasNext()) {
SelectionKey key = nitr.next();
nitr.remove();
if (!key.isValid()) {
continue;
}
try {
if (key.isAcceptable()) {
onAcceptable(key);
} else if (key.isConnectable()) {
onConnectable(key);
} else {
if (key.isReadable()) {
onReadable(key);
}
if (key.isWritable()) {
onWritable(key);
}
}
} catch (IOException | InterruptedException | CancelledKeyException ex) {
ex.printStackTrace(Log.logWriter);
//...
}
}
}