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);
            //...
        }
    }
}