带有多线程的Java NIO SocketChannel.read()

时间:2012-07-03 06:12:37

标签: java multithreading nio

我正在使用Java NIO实现一个简单的文件服务器,其中包含一个选择线程和多个工作线程(用于执行实际的读/写)。

代码的主要部分如下所示:

while (true) {
    int num = selector.select();
    if (num > 0) { 
        Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
        final SelectionKey key = keys.next();
        keys.remove();

        if (key.isValid()) {
            if (key.isAcceptable()) {
                accept(key);
            } else if (key.isReadable()) {
                performReadInWorkerThread (key);
            } else if (key.isWritable()) {
                performWriteInWorkerThread (key);
            }
        }
    }
}

从代码片段中可以看出,当选择了可读/可写通道时,我将读/写从选择线程卸载到工作线程。

现在的问题是,当一个可读通道被移交给工作线程时,在它完成/开始从通道读取之前,选择线程再次循环,selector.select()选择先前选择的可读通道(因为通道中仍有输入缓冲区尚未被先前分配的工作线程完全占用,所以再次将通道移交给另一个工作线程,导致多个工作线程读取相同的通道

我认为这是一个设计问题。我的问题是如何确保只有一个线程同时读取一个频道?

3 个答案:

答案 0 :(得分:7)

为什么呢?读取不会阻止。在当前线程中执行此操作。你只是在这方面遇到无穷无尽的问题。在移交给读取线程之前,您必须取消注册OP_READ,这很容易,但困难的是当读取线程完成读取时,它必须重新注册OP_READ,这需要(i)选择器wakeup(),导致select线程在可能无事可做时运行,这是浪费,或者(ii)使用挂起重新注册队列,这会延迟该通道上的下一次读取,直到下一次选择器之后醒来,这也是浪费,否则你必须立即在添加到队列时唤醒选择器,如果没有准备好也是浪费。我从未见过使用不同选择和读取线程的令人信服的NIO架构。

不要这样做。如果您必须具有多线程,请将您的频道组织成组,每个组都有自己的选择器和自己的线程,并让所有这些线程都自己阅读。

同样,不需要在单独的线程中编写。只要你有写东西就写。

答案 1 :(得分:3)

对于NIO,请记住一个原则: 在主选择线程中进行读/写。 这个原则是硬件性质的反映。 不要担心主选择线程中的读取不是很快。在现代服务器中,CPU总是比网卡快。因此,一个线程中的无阻塞读取也比网卡操作更快。一个线程已足以读取数据包。我们不需要再有线程了。

答案 2 :(得分:-1)

检查SelectionKey.cancel()方法。