NIO2 CompletionHandler 的线程安全性

7
以下代码是线程安全的吗?如果是,是什么保证了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或者是“有效的final”。无论如何,你都不应该不断地重新分配读取缓冲区。在通道的生命周期内使用同一个缓冲区,并将其保留在键附加中或通过键附加来使用。 - user207421
@user207421 是的,在这种情况下,它会编译,因为 buf 在实质上是最终的。关于缓冲区分配-这是一个样例代码,用于解答问题。 - Malt
3个回答

3

一份权威的参考资料,适用于所有JVM和所有平台

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

不幸的是,正如您自己所看到的,它们没有明确和清晰的线程安全保证。

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


我认为应该在方法的方法所在类的CompletionHandler的javadoc中给出保证,这样我们就可以确信它们在所有JVM和所有平台上都得到了实现(并且将来会继续得到实现)。

但如果您真的想要,您可以从不同的javadoc文档中编译出线程安全的证明:

  • AsynchronousSocketChannel.read(...):

    handler参数是一个完成处理程序,在读取操作完成(或失败)时调用。

  • java.nio.channels:

    异步通道绑定到异步通道组,以实现资源共享。组具有关联的ExecutorService,将任务提交以处理I/O事件,并将其分派到消耗在组中通道上执行的异步操作结果的完成处理程序。

  • ExecutorService:

    内存一致性效果:在将Runnable或Callable任务提交给ExecutorService之前,线程中的操作发生在该任务执行的任何操作之前。

因此,我们得出结论,ByteBuffer 的每个 I/O 读取操作在 CompletionHandler 的第一个操作之前发生,这意味着代码是线程安全的。
我认为像上面那样的“编译证明”太过脆弱,个人而言,我会假设该代码不是线程安全的。

2
我在Javadocs中没有看到任何有关在read()操作中使用的ByteBuffer内部状态在完成read()时被CompletionHandler看到的明确保证。因此,这并不是100%的保证。但我认为,这是常识,应该是正确的,原因如下:
  1. 通常期望CompletionHandler能够看到由“read()”所做的更改
  2. CompletionHandler可以在不同的线程中异步运行,这是read()内部实现的细节。因此,read()需要确保在这种情况下安全地发布事物。
但如果您不确定或者不相信,并且您不会开发具有纳秒延迟的应用程序,只需添加自己的额外同步,您将百分之百确定您的程序可以正常工作。

感谢您的输入。我倾向于同意这应该是线程安全的,我也意识到我可以使用锁来确保它是线程安全的。然而,问题不在于此。问题在于什么保证跨JVM、操作系统和CPU架构实现这种行为。 - Malt

1

只是为了补充 上面的答案, 你可能更喜欢通过read(...)方法的attachment参数将ByteBuffer传递给CompletionHandler(就像在各种 示例中所做的那样):

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的线程中。
不幸的是,在javadoc中我没有看到任何明确的保证,即这种安全发布发生在read(...)完成之后


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接