使Java的ByteBuffer线程安全的选项

14
我有哪些选项可以使ByteBuffer线程安全?已知它不是线程安全的,因为它保存了位置、限制和一些(或全部?)方法依赖于这个内部状态。

对于我的目的来说,如果多个读线程是安全的,就足够了,但是为了其他未来的访问者,我想知道我需要知道什么技巧/诀窍/陷阱才能使它完全线程安全。

我想到的:

  • 对所有方法进行同步或使用ReadWrite锁。可能是最慢的方法(?)
  • 子类化ByteBuffer并避免持久化线程绑定状态,如position等,并相应地为需要使用内部状态的所有方法抛出异常。这将是最快的。但是是否有任何陷阱?(除了我必须将直接映射的内存读入堆内存之外...)

我还能用什么诀窍吗?例如,我怎么实现“在读取时复制字节”与DirectBuffer-是否可能?可能会涉及到一个解决方案中的完整ByteBuffer(ByteBuffer.slice)吗?

更新:在这个问题中,“在同步时复制以获得一个指向相同映射字节的新实例”是什么意思?


1
我更喜欢使用Actor模型,其中任何ByteBuffer仅由一个线程更新(或只读)。我这样做的原因是我发现同步的开销通常超过了拥有多个线程的好处。 - Peter Lawrey
谢谢,彼得!你是通过切片使用它的吗?还是你是如何访问相同的数据的? - Karussell
我不使用切片,而是使用一个包装器来隐藏所有内容(甚至隐藏我使用的ByteBuffers以及可能想要切换到略微更快的Unsafe)。使用包装器的好处是可以隐藏所有细节,并通过调用方法自然地访问您的“数组”。这尤其有用,因为我有数十亿个元素的数组无法适合单个ByteBuffer中。 - Peter Lawrey
我很幸运,你的缓冲包装器是开源的吗? ;) - Karussell
每个编年史都是单线程的,您需要为每个线程或进程创建一个编年史。由于每个线程更快,因此您只需要很少的线程(可能只需要一个)。它不使用锁,并且大多数情况下没有堆内存使用。 - Peter Lawrey
显示剩余4条评论
2个回答

13

虽然可以通过恰当同步各个操作等方式使Buffer类变得线程安全,但API并未考虑多线程的情况,因此这样做可能是浪费时间。

基本问题在于Buffer上的单个操作过于细粒度,不能成为同步的单位。 应用程序不能有效地在获取、放置操作或翻转、定位等级别进行同步。一般来说,应用程序需要原子地执行这些操作序列以有效同步。

第二个问题在于,如果您在细粒度层面上同步,这可能会增加方法调用的重要开销。 由于使用缓冲区API的目的是高效进行I/O,这会打败他的目的。


如果确实需要同步线程对共享缓冲区的访问,最好使用外部同步;例如以下方法:

    synchronized (someLock) {
        buffer.getByte();
        buffer.getLong();
        ...
    }
只要所有使用给定缓冲区的线程正确同步(例如使用相同的锁对象),就不必考虑缓冲区是否是线程安全的。线程安全由缓冲区对象外部以更粗粒度的方式进行管理。
正如评论所指出的,您还可以使用ByteBuffer.slice()buffer.asReadOnlyBuffer()来获得另一个具有现有缓冲区作为后备的缓冲区。但是,javadocs并没有保证这两种情况下的线程安全性。事实上,对于Bufferjavadocs做出了这样的概括性陈述:
"缓冲区不能被多个并发线程安全使用。如果一个缓冲区将被多个线程使用,则应通过适当的同步来控制对缓冲区的访问。"

我认为只读情况并没有简化事情,除非你准备将缓冲区视为一个简单的数组...通过array()直接获取和使用后备数组。即使这样,你仍然需要一些同步来确保内存访问的一致性。 - Stephen C
2
没有写入操作,怎么可能不一致呢?此外,支持数组并不总是存在。 - Karussell
1
@Karussell - 1) 因为大多数操作都会更新缓冲区的“位置”。2) 是的,这是真的。但要么这样做,要么使用getXxx(pos)方法。 - Stephen C
好的,谢谢!如果我有一个只读操作,并使用ByteBuffer.slice(以便我可以为每个线程使用一个ByteBuffer)-还有什么需要注意的吗? - Karussell
当您将使用buffer.asReadOnlyBuffer()创建的副本传递给每个线程时,您可以拥有可共享的只读缓冲区。它们将共享内部数据数组,但每个线程都将拥有自己的limitpositionmark副本。 - Oliv

0

使用JDK13,您现在可以在没有byteBuffer.position(int)的情况下使用ByteBuffer,并获得线程安全。

请参阅发行说明

java.nio.ByteBuffer和java.nio中的其他缓冲区类型现在定义了绝对批量获取和放置方法,以传输连续的字节序列,而不考虑或影响缓冲区位置。


3
这与线程安全无关。即使 JDK-16 的 Buffer javadoc 明确指出“缓冲区不适用于多个并发线程”,也不能保证其安全性。 - Harald

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