【问题标题】:Options to make Java's ByteBuffer thread safe使 Java 的 ByteBuffer 线程安全的选项
【发布时间】:2012-06-24 14:21:21
【问题描述】:

我有哪些选项可以使 ByteBuffer 线程安全?众所周知,它不是线程安全的,因为它保护位置、限制和一些(/全部?)方法取决于这个内部状态。

就我的目的而言,如果多个读取线程是安全的就足够了,但对于其他未来的访问者,我想知道我需要知道哪些技术/技巧/陷阱才能使其完全线程安全。

我的想法:

  • 为所有方法同步或使用读写锁。可能是最慢的方法 (?)
  • 子类化 ByteBuffer 并避免持久化线程绑定状态,如位置等。并为所有需要使用内部状态的方法相应地抛出异常。这将是禁食。但是有什么陷阱吗? (除了我必须将直接映射的内存读入堆内存......)

我还可以使用哪些其他技巧?我会怎么做?使用 DirectBuffer 实现“读取时克隆字节” - 有可能吗?可能会在一个解决方案中涉及对完整的 ByteBuffer (ByteBuffer.slice) 进行切片吗?

更新question 中的含义 “复制(同步时)以获取指向相同映射字节的新实例”

【问题讨论】:

  • 我更喜欢使用一个参与者模型,其中任何 ByteBuffer 仅由一个线程更新(或只读)。我这样做的原因是我发现同步的开销通常超过了拥有多个线程的好处。
  • 谢谢,彼得!你是通过切片使用这个吗?或者您如何访问相同的数据?
  • 我不使用切片,而是使用隐藏所有内容的包装器(即使我正在使用 ByteBuffers 并且我可能想要切换到更快的 Unsafe)包装器是您可以隐藏所有细节并通过调用方法自然地访问您的“数组”。这特别有用,因为我有包含数十亿个元素的数组,这些元素不适合单个 ByteBuffer。
  • 我运气好,而你的缓冲区包装器是开源的;)?
  • 不,但我有一个类似的开源库。 github.com/peter-lawrey/Java-Chronicle 这是基于条目/行/摘录而不是基于列,但技术是相同的。

标签: java thread-safety bytebuffer


【解决方案1】:

Buffer 类可以 被制成线程安全的......从某种意义上说,各个操作已正确同步,等等。但是,API 的设计并未考虑多线程,所以这可能是浪费时间。

基本问题是 Buffer 上的单个操作过于细粒度而不能成为同步单元。应用程序无法在 get 和 put 操作或翻转、位置等级别进行有意义的同步。一般来说,应用程序需要原子地执行这些操作的序列才能有效地同步。

第二个问题是,如果您确实在精细级别进行同步,这可能会在方法调用上增加大量开销。由于使用 Buffer API 的目的是高效地进行 I/O,因此这与目的背道而驰。


如果确实需要同步线程访问共享缓冲区,最好使用外部同步;例如像这样:

    synchronized (someLock) {
        buffer.getByte();
        buffer.getLong();
        ...
    }

如果使用给定缓冲区的所有线程正确同步(例如,使用相同的锁对象),则缓冲区不是线程安全的并不重要。线程安全在缓冲区对象外部进行管理,并且以更粗粒度的方式进行。


正如 cmets 指出的那样,您还可以使用 ByteBuffer.slice()buffer.asReadOnlyBuffer() 为您提供另一个缓冲区,并以现有缓冲区作为支持。但是,javadocs 在这两种情况下都不保证线程安全。事实上,javadocs 代表 Buffer 做出这样的概括性声明:

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

【讨论】:

  • 我认为只读情况不会简化事情,除非您准备将缓冲区视为一个简单的数组...直接通过array() 获取并使用支持数组。即使这样,您也需要进行一些同步以确保一致地访问内存。
  • 不写怎么会不一致呢?支持数组也不总是存在
  • @Karussell - 1) 因为大多数操作都会更新缓冲区的“位置”。 2)是的,这是真的。但要么就是那个,要么就是 getXxx(pos) 方法
  • 好的,谢谢!假设我将有一个只读操作并使用 ByteBuffer.slice(这样我就可以为每个线程使用一个 ByteBuffer)——它们仍然是任何陷阱吗:)?
  • 当您将使用buffer.asReadOnlyBuffer() 创建的副本传递给每个线程时,您可以拥有可共享的只读缓冲区。他们将共享内部数据数组,但每个人都有自己的 limitpositionmark 副本。
【解决方案2】:

使用 JDK13,您现在可以使用不带 byteBuffer.position(int) 的 ByteBuffer 并获得线程安全。

请参阅release notes

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

【讨论】:

  • 这与线程安全无关。甚至 Buffer 的 jdk-16 javadoc 也明确指出“缓冲区对于多个并发线程的使用是不安全的”。
猜你喜欢
  • 1970-01-01
  • 2010-11-14
  • 2015-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多