5

考虑一个请求-响应协议。

我们生成一个线程来执行一个select()循环,以便在接受的非阻塞 上进行读取和写入SocketChannel。这可能看起来像

while (!isStopped()) {
    selector.select();
    Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();

    while (selectedKeys.hasNext()) {
        SelectionKey selectedKey = selectedKeys.next();
        selectedKeys.remove();
        Context context = (Context) selectedKey.attachment();
        if (selectedKey.isReadable()) {
            context.readRequest();
        } else /* if (selectedKey.isWritable()) */ {
            context.writeResponse();
        }
    }
}

whereContext只是相应的容器SocketChannel,缓冲区和读取它并从中写入的逻辑。可能readRequest看起来像

public void readRequest() {
    // read all content
    socketChannel.read(requestBuffer);
    // not interested anymore
    selectionKey.interestOps(0);
    executorService.submit(() -> {
        // handle request with request buffer and prepare response
        responseBuffer.put(/* some response content */); // or set fields of some bean that will be serialized

        // notify selector, ready to write
        selectionKey.interestOps(SelectionKey.OP_WRITE);
        selectionKey.selector().wakeup(); // worried about this
    });
}

换句话说,我们从套接字通道读取数据,填充一些缓冲区并将处理交给其他线程。该线程进行处理并准备将其存储在响应缓冲区中的响应。然后它通知选择器它想要写入并唤醒它。

JavadocSelector#wakeup()没有提到任何发生前的关系,所以我担心选择器线程可能会看到响应缓冲区(或某些中间对象)处于不一致的状态。

这是可能的情况吗?如果是,那么将响应写入循环线程SocketChannel的正确方法是什么?Selector(通过某个字段发布响应volatile?使用SelectionKey附件?其他形式的同步?)

4

2 回答 2

3

首先,你不需要通知选择器想要写。你就写吧。只有在写入返回零的情况下,才需要涉及选择器或其线程。

其次,happens-before 关系是选择器的三个同步级别的结果,前提是您还进行了同步,如下所示。

如果选择器当前正在选择,您的代码可能会在调用中阻塞。interestOps()Javadoc 不排除这种可能性。您需要按正确的顺序执行操作:

  1. 醒来。
  2. 在选择器上同步。
  3. 打电话interestOps()

(2) 和选择器自己的内部同步的组合建立了任何必要的先发生关系。

于 2017-10-09T18:16:19.680 回答
2

关于文档Selector的内容如下:

选择操作按顺序在选择器本身、键集和选定键集上同步。

发生前关系Java 语言规范第 17 章中定义为包括synchronizes-with关系。

即使这样,您也应该在附加对象上正确同步。那是你的目标,这是你的职责。假设只有您的代码在执行程序的线程中写入,并且在您说您对写入可用性感兴趣之后responseBuffer只有选择器线程从中读取,那么您有足够的同步。

可能会让您感到惊讶的是,您interestOps(...)甚至在之前就已经从wakeup().


根据我的经验,如果您很难通过库实用程序(在本例中为选择器)来实现正确的同步,您最好自己同步您的对象,例如与synchronize对象本身的语句、a ReentrantLock、其他一些常见的您在对象操作等上使用的同步对象。为了保持冷静,您会损失一点点性能(实际上,在大多数情况下,这并不重要,假设您没有在受保护的部分内阻塞)。

于 2017-10-16T14:54:31.160 回答