为什么ByteBuffer的绝对读取不被认为是线程安全的?

Pau*_*ora 7 java multithreading nio bytebuffer

我的用例需要直接分配ByteBuffer,写入一次,然后由许多并发线程读取.所有读取都是绝对的,所以我从不关心缓冲区的状态(位置,限制,标记).

关于Keith Gregory的字节缓冲区的这篇文章警告说,即使绝对读取也不被认为是线程安全的:

ByteBufferBufferJavaDoc中介绍了线程安全性; 简短的版本是缓冲区不是线程安全的.显然,在没有竞争条件的情况下,您不能使用来自多个线程的相对定位,但即使绝对定位也无法保证(无论您在查看实现类后如何看待).

(强调我的)

由于这个警告,我在字节缓冲区的每次读取之前调用duplicate.这很容易,但每次读取时额外的对象分配让我很好奇为什么它实际上是必要的.

尽管Keith的向导免责声明,我确实看到了OpenJDK对直接字节缓冲区的绝对读取的实现:

public byte get(int i) {
    return ((unsafe.getByte(ix(checkIndex(i)))));
}
Run Code Online (Sandbox Code Playgroud)

您可以看到它只是委托给Unsafe.getByte(long)"从给定内存地址获取值".

我知道可能存在不同的实现,但对于此操作而言,合理地不能保证线程安全吗?Buffer合同是否只是拒绝保证绝对读取的线程安全性,以避免部分线程安全类的混淆?或者如果警告对于并发写入是合理的,那么我的情况如何,在创建后字节缓冲区未被修改?此外,使用MappedByteBuffer替代品时会有什么变化吗?

有关:

Gra*_*ray 1

为什么从 ByteBuffer 进行的绝对读取不被认为是线程安全的?

考虑到可以位于 之下的直接内存缓冲区(想想 mmap 和其他硬件支持的文件)的性质ByteBuffer,任何外部更新都保证不会正确同步——这不好。此外,事实上slice()duplicate()被设计为使用相同的缓冲区,这意味着尽管 ByteBuffer可能以线程安全的方式使用,但可能无法保证“源”缓冲区是。这些复杂性可能是使用一揽子不适用于线程警告的原因。

由于此警告,我在每次从字节缓冲区读取数据之前都会调用重复调用。

正如您提到的,不同的 JDK 和/或 Java 版本可能有不同的实现,但我同意,只要您小心,我认为使用不同步的缓冲区不会有任何问题。

  1. 支持的内存ByteBuffer不得被任何外部实体修改。
  2. 在创建线程或保存缓冲区的字段需要之前,您必须实例化ByteBuffer并进行任何变异方法调用(ieeput(...)等)。volatile
  3. 您的缓冲区可能不应该是切片、重复的或以其他方式与任何其他缓冲区共享字节。
  4. 线程仅get(...)使用非变异方法。这意味着(就像您提到的)您不能依赖该offset字段的值。但也要小心array()返回实际数组缓冲区而不是副本的方法。返回数组的任何变化都不是线程安全的。

Keith 的文章暗示了其中的一些内容:

也就是说,您仍然存在创建缓冲区的问题:您需要同步对 slice() 或重复() 调用的访问。一种方法是在生成线程之前创建所有缓冲区。但是,这可能会很不方便,特别是当您的缓冲区位于另一个类的内部时。另一种方法是使用 ThreadLocal...

与所有线程问题一样,问题在于细节,我承认Unsafe.getByte(...)除了ByteBuffer.wrap(...).

另外,使用 MappedByteBuffer 时会有什么变化吗?

的使用MappedByteBuffer使得上述第一点变得至关重要。另外,再次强调,任何调用都load()必须以线程安全的方式进行(参见#2)。

如果有任何问题,需要考虑的一件事是使用ByteBuffer.wrap(...)和不使用加载机制和直接内存的东西。当然,您应该运行一些性能测试来查看影响是什么,因为您可能会发现使其适当同步(在内存意义上)可能比“手动”加载更昂贵。小心过早的优化。

祝你好运。