NIO2 CompletionHandler 的线程安全
Posted
技术标签:
【中文标题】NIO2 CompletionHandler 的线程安全【英文标题】:Thread-safety of NIO2 CompletionHandler 【发布时间】:2021-10-26 08:50:17 【问题描述】:以下代码是线程安全的吗?如果是这样,什么保证ByteBuffer
实例安全发布到执行CompletionHandler
的线程?
AsynchronousSocketChannel channel = ...
ByteBuffer buf = ByteBuffer.allocate(1024);
channel.read(buf, null, new CompletionHandler<Integer, Void>()
//"completed" can be executed by a different thread than channel.read()
public void completed(Integer result, Void attachment)
buf.flip(); //Can buf be safely accessed here? If so, why?
//...
public void failed(Throwable exc, Void attachment)
//...
);
【问题讨论】:
这可以编译吗?buf
必须是 final
或此处的“有效最终”。在任何情况下,您都不应该继续重新分配读取缓冲区。在频道的整个生命周期内使用相同的密钥,并将其保存在密钥附件中或通过密钥附件保存。
@user207421 是的,它可以编译,因为buf
在这种情况下实际上是最终的。关于缓冲区分配 - 这是用于问题目的的示例代码。
【参考方案1】:
对所有 JVM 和所有平台都有效的权威参考
我所知道的唯一这样的权威来源是 javadocs(1、2、3)。
不幸的是,正如您亲眼所见,它们不包含对线程安全的明确和明确的保证。
这意味着代码不是线程安全的。
IMO 应在 javadoc 中为 the method、the method's class 或 CompletionHandler
提供保证——然后我们可以确定它们已在所有 JVM 和所有平台上实现(并将在未来保持实现)。
但如果你真的想要,你可以从不同的 javadocs 中的多个地方“编译”一个线程安全证明:
AsynchronousSocketChannel.read(...)
:
handler 参数是一个完成处理程序,在读取操作完成(或失败)时调用。
java.nio.channels
:
为了资源共享,异步通道绑定到一个异步通道组。组具有关联的 ExecutorService,任务被提交到该 ExecutorService 以处理 I/O 事件并分派到完成处理程序,这些处理程序使用在组中的通道上执行的异步操作的结果。
ExecutorService
:
内存一致性影响:线程中的操作在将 Runnable 或 Callable 任务提交给 ExecutorService 之前发生 - 在该任务采取的任何操作之前
因此,我们得到 I/O 的每个操作都读取到 ByteBuffer
发生在CompletionHandler
的第一个操作 => 这意味着代码是线程安全的。
像上面这样的 IMO“编译证明”太脆弱了,我个人认为代码不是线程安全的。
【讨论】:
【参考方案2】:只需添加to the answer above,您可能更愿意通过attachment
方法的attachment
参数将ByteBuffer
传递给CompletionHandler
(就像在various examples 中完成的那样):
AsynchronousSocketChannel channel = ...
ByteBuffer buf = ByteBuffer.allocate(1024);
channel.read(buf, buf, new CompletionHandler<>()
public void completed(Integer result, ByteBuffer buf)
buf.flip();
//...
public void failed(Throwable exc, ByteBuffer buf)
//...
);
由于attachment
是read(...)
的显式参数,它必须安全地发布到CompletionHandler
的线程。
不幸的是,我在 javadocs 中没有看到任何明确保证此安全发布发生在read(...)
完成之后。
【讨论】:
【参考方案3】:我在 javadocs 中没有看到任何关于 ByteBuffer
内部状态的明确保证,在 read()
操作中使用,在 read()
完成时调用的 CompletionHandler
内部可见。
所以没有 100% 的保证。
但我想说的是,期望这是真的是常识。 原因很简单:
-
期待
CompletionHandler
看到“read()”所做的更改是正常的
CompletionHandler
可以在不同的线程中异步运行这一事实——这是read()
内部实现的一个细节。因此,read()
关心的是确保在这种情况下安全发布该内容。
但如果您不确定/不相信,并且您不开发具有纳秒级延迟的应用程序 - 只需添加您自己的额外同步 - 您将 100% 确定您的程序正常运行。
【讨论】:
感谢您的意见。我倾向于同意这是线程安全的,并且我意识到我可以使用锁来确保它是线程安全的。然而,问题不在于这个。它是关于 什么保证 跨 JVM 的这种行为。操作系统和 CPU 架构。以上是关于NIO2 CompletionHandler 的线程安全的主要内容,如果未能解决你的问题,请参考以下文章