考虑请求 - 响应协议。如何在请求处理线程和SocketChannel选择器线程之间建立一个前后关系?
我们产生了一个线程来执行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();
}
}
}
其中Context
仅仅是相应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
});
}
换句话说,我们从套接字通道读取,填充一些缓冲和挂断处理一些其他线程。该线程完成处理并准备将其存储在响应缓冲区中的响应。然后通知选择器它想要写入并唤醒它。
Javadoc Selector#wakeup()
没有提及任何发生之前的关系,所以我担心选择器线程可能会看到响应缓冲区(或某个中间对象)处于不一致状态。
这是一种可能的情况?如果是这样的话,有什么正确的方法可以通过Selector
循环线程将响应写入SocketChannel
? (通过一些volatile
字段发布响应?使用SelectionKey
附件?一些其他形式的同步?)
这似乎是[这个问题](https://stackoverflow.com/q/28754275/149138),它也没有很好的答案。我的回答可能是,在实际中,在一个线程上的wakeup()和另一个接收唤醒的线程上的select()调用之间发生了一个事件之前的关系。这有两个实际原因:因为它以任何其他方式工作都会使API非常无用,典型实现的内部机制将涉及相同类型的原子和锁定原语,这些原语会在订购前执行。我认为这是一个文档缺陷。 – BeeOnRope