【问题标题】:Why are absolute reads from a ByteBuffer not considered thread-safe?为什么从 ByteBuffer 中的绝对读取不被认为是线程安全的?
【发布时间】:2023-03-04 15:47:01
【问题描述】:

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

This article Keith Gregory 在字节缓冲区上警告说,即使是绝对读取也不被认为是线程安全的:

ByteBuffer 线程安全包含在Buffer JavaDoc 中;简短的版本是缓冲区不是线程安全的。显然,您不能在没有竞争条件的情况下使用来自多个线程的相对定位,但即使是绝对定位也不能保证(无论您在查看实现类后会怎么想)

(强调我的)

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

尽管 Keith 有魔法般的免责声明,但我确实查看了 OpenJDK 的 implementation 从直接字节缓冲区的绝对读取:

public byte get(int i) {
    return ((unsafe.getByte(ix(checkIndex(i)))));
}

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

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

相关:

【问题讨论】:

  • 嗯。那里有很多未经证实的意见。例如“我认为这没有考虑到 Java 内存模型”。实现必须 (a) 遵守 Javadoc 中规定的规范,并且 (b) 这样做时要考虑 Java 内存模型。
  • @EJP Java 内存模型评论似乎指的是直接缓冲区,它位于 Java 堆之外。它们基本上是内存映射的 IO 操作,对文件(或 Java 堆/堆栈/等之外的任何东西)的 IO 操作不必遵守 Java 内存模型
  • 即使在没有适当同步的情况下忽略可见性问题,get 方法也不是原子的。它访问共享数据数组并执行索引检查,其中涉及读取共享状态limit
  • Nothing 是线程安全的,除非某种机制使其成为线程安全的。
  • @PaulBellora - 我添加了该行以响应在线程之间共享缓冲区的同事。我对你的回答和他的一样:是的,它在我看来确实是线程安全的,但是如果我们使用不是线程安全的 JVM,我不想更改很多代码。我相信线程本地副本(在那篇文章的下方显示)是最好的方法。我当然不会在每次使用时都重复。

标签: java multithreading nio bytebuffer


【解决方案1】:

一方面,Buffer 文档指出对缓冲区的访问应该是同步的。它并没有说它不能被不同的线程使用。所以我认为不需要duplicate

您无法想到某种方法或其他方法的合理的非线程安全实现,这更多地限制了您的想象力,而不是证明没有必要谨慎。特别是考虑到您没有查看 Oracle Java 代码,当时 Oracle 声明该实现不是线程安全的。

我的建议:在访问缓冲区时做一些合理的同步。即使永远不会有非线程安全的实现,它也不会花费你太多。

【讨论】:

  • 我更喜欢使用duplicate 而不是同步以避免争用。不幸的是,你的中间段落更多的是评论而不是答案 - 我缺乏证据和有限的想象力是我问这个问题的原因。
  • @Paul 很明显,实现一次读取/写入一个字节会导致读取/写入撕裂。
  • @Paul duplicate 本身不是线程安全的,因此如果没有适当的同步,您也可能无法从不同的线程中使用它。
猜你喜欢
  • 2011-08-25
  • 1970-01-01
  • 2021-02-04
  • 1970-01-01
  • 2014-03-08
  • 2014-03-26
  • 2011-08-13
  • 2012-11-19
  • 2020-01-03
相关资源
最近更新 更多