java.nio.ByteBuffer.slice() 线程行为?

Posted

技术标签:

【中文标题】java.nio.ByteBuffer.slice() 线程行为?【英文标题】:java.nio.ByteBuffer.slice() Threading Behaviour? 【发布时间】:2020-01-29 09:21:09 【问题描述】:

我了解 java.nio.ByteBuffer 本身不是线程安全的。但是,如果您通过 slice() 获得一个共享的、派生的 ByteBuffer,您是否能够通过不同的切片缓冲区在多个线程中同时访问底层缓冲区的内容?我在 API 规范中找不到任何关于此的内容...如果此行为未标准化,您知道它是如何在最常见的 VM 中实现的吗?

【问题讨论】:

【参考方案1】:

基本上,如果某些东西没有被证明是线程安全的,那么就假设它不是;如果某些东西被明确记录为 not 是线程安全的,除非另有说明,否则永远不要假设任何密切相关的东西都是线程安全的。


正如您提到的,缓冲区不是线程安全的。 Buffer 记录了这一点:

缓冲区对于多个并发线程的使用是不安全的。如果一个缓冲区要被多个线程使用,那么对缓冲区的访问应该由适当的同步控制。

并且扩展BufferByteBuffer 的文档与上述内容并不矛盾。

ByteBuffer#slice() 的文档是这样说的:

创建一个新的字节缓冲区,其内容是该缓冲区内容的共享子序列 [强调]。

新缓冲区的内容将从该缓冲区的当前位置开始。 此缓冲区内容的更改将在新缓冲区中可见,反之亦然;这两个缓冲区的位置、限制和标记值将是独立的。 [强调添加]

新缓冲区的位置为零,其容量和限制将是此缓冲区中剩余的字节数,其标记将未定义,其字节顺序将为BIG_ENDIAN。当且仅当此缓冲区是直接的时,新缓冲区才会是直接的,并且当且仅当此缓冲区是只读的时,它将是只读的。

其他类似的方法,例如#slice(int,int)#alignedSlice(int),记录了类似的行为。

如您所见,缓冲区实例的内容是共享的。文档没有提到在这种情况下添加线程安全的任何内容,因此我们可以自信地假设缓冲区的一般线程安全适用——也就是说,没有线程安全。如果写入共享相同内容子序列的任何缓冲区,则所有其他缓冲区都将受到影响。在并发上下文中,如果没有适当的外部同步,这意味着潜在的竞争条件。

我不确定这如何适用于读取和写入不同(即非重叠)子序列。我假设适用于数组的任何行为都适用于这种情况。当然,这并没有考虑到直接缓冲区。

话虽如此,但其中有一些微妙之处。如文档所述,每个缓冲区都有独立的位置、限制和标记值。这样做的结果是每个缓冲区都可以由单独的线程读取。但是,这是缓冲区和线程之间的一对一映射(除非您添加外部同步)1。这是因为位置和标记值可以通过读取缓冲区(至少 relative 读取操作1 的情况)和倒带来修改。


1.我相信当且仅当多个线程都使用 absolute 读取操作并且不使用标记时,多个线程可以从同一个缓冲区实例中读取而无需同步。换句话说,只要没有线程修改缓冲区的“元状态”。

【讨论】:

很好的答案,已经赞成,但关于你的脚注,我怀疑读取异步线程可以读取ByteBuffer,而它正在被其原始线程更改(文件线程异常会引发)。我是否正确理解了您的脚注? @Iman 我的脚注只适用于阅读——并且 absolute 阅读操作。如果缓冲区被写入,那么在多线程上下文中绝对需要同步。如果任何线程执行相对读取操作,同样适用,因为它写入缓冲区的“元状态”。 @Iman 重要的是保持 happens-before 关系。如果没有线程写入任何状态,则无需担心(假设原始状态已正确发布)。

以上是关于java.nio.ByteBuffer.slice() 线程行为?的主要内容,如果未能解决你的问题,请参考以下文章