【问题标题】:Thread-safety of NIO2 CompletionHandlerNIO2 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 在这种情况下实际上是最终的。关于缓冲区分配 - 这是用于问题目的的示例代码。

标签: java java-memory-model nio2


【解决方案1】:

对所有 JVM 和所有平台都有效的权威参考

我所知道的唯一这样的权威来源是 javadocs(123)。

不幸的是,正如您亲眼所见,它们不包含对线程安全的明确和明确的保证。

这意味着代码不是线程安全的。


IMO 应在 javadoc 中为 the methodthe method's classCompletionHandler 提供保证——然后我们可以确定它们已在所有 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) {
           //...
        }
    });
    

    由于attachmentread(...) 的显式参数,它必须安全地发布到CompletionHandler 的线程。
    不幸的是,我在 javadocs 中没有看到任何明确保证此安全发布发生在read(...) 完成之后。

    【讨论】:

      【解决方案3】:

      我在 javadocs 中没有看到任何关于 ByteBuffer 内部状态的明确保证,在 read() 操作中使用,在 read() 完成时调用的 CompletionHandler 内部可见。

      所以没有 100% 的保证。

      但我想说的是,期望这是真的是常识。 原因很简单:

      1. 期待CompletionHandler 看到“read()”所做的更改是正常的
      2. CompletionHandler 可以在不同的线程中异步运行这一事实——这是read() 内部实现的一个细节。因此,read() 关心的是确保在这种情况下安全发布该内容。

      但如果您不确定/不相信,并且您不开发具有纳秒级延迟的应用程序 - 只需添加您自己的额外同步 - 您将 100% 确定您的程序正常运行。

      【讨论】:

      • 感谢您的意见。我倾向于同意这是线程安全的,并且我意识到我可以使用锁来确保它是线程安全的。然而,问题不在于这个。它是关于 什么保证 跨 JVM 的这种行为。操作系统和 CPU 架构。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-30
      • 2021-05-19
      • 2022-11-21
      • 1970-01-01
      • 2021-07-12
      • 1970-01-01
      相关资源
      最近更新 更多