Pau*_*ora 7 java multithreading nio bytebuffer
我的用例需要直接分配ByteBuffer
,写入一次,然后由许多并发线程读取.所有读取都是绝对的,所以我从不关心缓冲区的状态(位置,限制,标记).
关于Keith Gregory的字节缓冲区的这篇文章警告说,即使绝对读取也不被认为是线程安全的:
ByteBuffer
Buffer
JavaDoc中介绍了线程安全性; 简短的版本是缓冲区不是线程安全的.显然,在没有竞争条件的情况下,您不能使用来自多个线程的相对定位,但即使绝对定位也无法保证(无论您在查看实现类后如何看待).
(强调我的)
由于这个警告,我在字节缓冲区的每次读取之前调用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
替代品时会有什么变化吗?
有关:
为什么从 ByteBuffer 进行的绝对读取不被认为是线程安全的?
考虑到可以位于 之下的直接内存缓冲区(想想 mmap 和其他硬件支持的文件)的性质ByteBuffer
,任何外部更新都保证不会正确同步——这不好。此外,事实上slice()
和duplicate()
被设计为使用相同的缓冲区,这意味着尽管您 ByteBuffer
可能以线程安全的方式使用,但可能无法保证“源”缓冲区是。这些复杂性可能是使用一揽子不适用于线程警告的原因。
由于此警告,我在每次从字节缓冲区读取数据之前都会调用重复调用。
正如您提到的,不同的 JDK 和/或 Java 版本可能有不同的实现,但我同意,只要您小心,我认为使用不同步的缓冲区不会有任何问题。
ByteBuffer
不得被任何外部实体修改。ByteBuffer
并进行任何变异方法调用(ieeput(...)
等)。volatile
get(...)
使用非变异方法。这意味着(就像您提到的)您不能依赖该offset
字段的值。但也要小心array()
返回实际数组缓冲区而不是副本的方法。返回数组的任何变化都不是线程安全的。Keith 的文章暗示了其中的一些内容:
也就是说,您仍然存在创建缓冲区的问题:您需要同步对 slice() 或重复() 调用的访问。一种方法是在生成线程之前创建所有缓冲区。但是,这可能会很不方便,特别是当您的缓冲区位于另一个类的内部时。另一种方法是使用 ThreadLocal...
与所有线程问题一样,问题在于细节,我承认Unsafe.getByte(...)
除了ByteBuffer.wrap(...)
.
另外,使用 MappedByteBuffer 时会有什么变化吗?
的使用MappedByteBuffer
使得上述第一点变得至关重要。另外,再次强调,任何调用都load()
必须以线程安全的方式进行(参见#2)。
如果有任何问题,需要考虑的一件事是使用ByteBuffer.wrap(...)
和不使用加载机制和直接内存的东西。当然,您应该运行一些性能测试来查看影响是什么,因为您可能会发现使其适当同步(在内存意义上)可能比“手动”加载更昂贵。小心过早的优化。
祝你好运。