在 Java 中原子读取然后写入部分 ByteBuffer

Posted

技术标签:

【中文标题】在 Java 中原子读取然后写入部分 ByteBuffer【英文标题】:Atomic read then write of part of a ByteBuffer in Java 【发布时间】:2012-02-17 12:46:16 【问题描述】:

我在 java 中有一个 ByteBuffer,想要读取,然后有条件地修改该字节,例如使用类似的方法:

public void updateByte(int index) 
    byte b = this.buffer.getByte(index);

    if (b == someByteValue) 
        this.buffer.setByte(index, someNewByte);
    

如何确保字节的读取和修改以原子方式发生?

我不想同步整个 ByteBuffer 或 updateByte 方法,因为我希望多个线程能够同时读取/写入缓冲区的不同字节(即 updateByte 可以同时调用很多线程,只要index 不同)。

我使用的 ByteBuffer 不是由 byte[] 支持的,所以在上面的例子中是 bb.hasArray() == false

【问题讨论】:

【参考方案1】:

如何为 ByteBuffer 的某些部分提供一组显式锁定对象(这些部分可能非常小,例如一个字,也可能非常大,例如四个四分之一缓冲区)?

当一个线程想要检查和修改一个字节时,它必须首先获取相应部分的锁,执行它的工作,然后释放锁。

这将允许多个线程访问数据的不同部分,而无需全局同步。

【讨论】:

【参考方案2】:

简短的回答:如果不求助于 JNI,你就做不到。

更长的答案:ByteBuffer API 中没有原子更新。此外,ByteBuffer 与内存的交互没有严格定义。而且在 Sun 实现中,用于访问原始内存的方法不会尝试刷新缓存,因此您可能会在多核处理器上看到陈旧的结果。

另外,请注意Buffer(及其子类,例如 ByteBuffer)被明确记录为不是线程安全的。如果您有多个线程访问同一个缓冲区,那么您 (1) 依赖实现行为进行绝对访问,或 (2) 编写损坏的代码以进行相对访问。

【讨论】:

没错,我将使用从 FileChannel 创建的 MappedByteBuffer(这是线程安全的),所以我认为映射的缓冲区也是安全的。 @Dave - MappedByteBuffer 与用于创建它的 FileChannel 没有连接。后者仅用于为 mmap 系统调用提供参数。 您可以创建和使用自己的 java.util.concurrent.lock.ReadWriteLock 实例。让所有读取/写入特定“原子字节”或“原子字节区域”的代码首先取出该字节/区域的相应锁。 :)【参考方案3】:

我不相信您可以在 Java 中以原子方式访问字节。您可以做的最好的事情是修改int 值。这将允许您模拟修改单个字节。

您可以使用 Unsafe(在许多 JVM 上)对 array()(堆 ByteBuffer)或 address()(直接 ByteBuffer)进行比较和交换

【讨论】:

【参考方案4】:

就我个人而言,我会锁定一个互斥锁,直到我确定要写入数据的偏移量,然后释放互斥锁。这样你就可以锁定很短的时间

【讨论】:

【参考方案5】:

list 上关于并发 DirectByteBuffer 的长长线程:

答案应该是“是”。

另一个大例子是 NIO.2。写/读操作提交字节缓冲区,调用CompletionHandler时就可以了。

因为,在 NIO.2 的情况下,只应用了 DirectByteBuffer。对于“克隆”到 DirectByteBuffer 的 Non-Direct-ByteBuffer,它不是真正的低级操作的参数。

【讨论】:

【参考方案6】:

应该可以锁定 ByteBuffer。方法:

您可以创建一个锁定对象列表,并且每次读取字节缓冲区时只锁定一个区域。就像 DNA 所暗示的那样,这应该是最快的解决方案。 或者您甚至可以使用memory mapping to solve this,然后使用FileChannel.lock,这也会锁定字节缓冲区的一个区域,但级别更低。 编辑:这仅保护来自外部程序 IMO 的访问 或者您可以使用几个较小但同步的 ByteBuffers + 交换信息。 interesting to note 线程应该立即看到彼此之间的变化(这就是我得到 mmap 想法的地方)

【讨论】:

【参考方案7】:

我认为将代码的关键部分置于锁定控制之下应该是干净的解决方案。但是,如果您的用例与写入相比读取次数较多,请不要直接使用同步。我建议您使用ReentrantReadWriteLock 作为解决方案的一部分。在修改 ByteBuffer 的函数中,您使用 writeLock().lock() 然后是您的代码。在阅读时使用 readLock().lock()。您可以在提到的链接上阅读更多关于读写锁的信息。基本上它将允许并发读取但不允许并发写入,并且在写入发生时读取线程等待

【讨论】:

以上是关于在 Java 中原子读取然后写入部分 ByteBuffer的主要内容,如果未能解决你的问题,请参考以下文章

JMM与并发三大特性

JMM与并发三大特性

AtomicLong可以被原子地读取和写入的底层long值的操作

nio buffer用法

并发编程中的原子性问题,可见性问题,有序性问题。

Java并发AtomicReferenceArray类