gpt4 book ai didi

java - AtomicReferenceFieldUpdater - 方法 set、get、compareAndSet 语义

转载 作者:行者123 更新时间:2023-12-01 21:15:59 26 4
gpt4 key购买 nike

From the Java AtomicReferenceFieldUpdater docs :

Note that the guarantees of the compareAndSet method in this class are weaker than in other atomic classes. Because this class cannot ensure that all uses of the field are appropriate for purposes of atomic access, it can guarantee atomicity and volatile semantics only with respect to other invocations of compareAndSet and set.



这意味着我不能与 compareAndSet 一起进行正常的 volatile 写入,但必须使用 set反而。它没有提到有关 get 的任何内容.

这是否意味着我仍然可以读取具有相同原子性保证的 volatile 字段 - 所有在 set 之前的写入或 compareAndSet每个阅读过 volatile 字段的人都可以看到吗?

或者我必须使用 getAtomicReferenceFieldUpdater而不是在场上进行 volatile 读取?

如果你有引用文献,请张贴。

谢谢你。

编辑:

来自 Java Concurrency in Practice ,他们唯一说的是:

The atomicity guarantees for the updater classes are weaker than for the regular atomic classes because you cannot guarantee that the underlying fields will not be modified directly — the compareAndSet and arithmetic methods guarantee atomicity only with respect to other threads using the atomic field updater methods.



同样,没有提到其他线程应该如何读取这些可变字段。

另外,我认为“直接修改”是常规的 volatile 写入是否正确?

最佳答案

the package documentation 中所述对于原子(一般来说,不是专门的更新程序):

The memory effects for accesses and updates of atomics generally follow the rules for volatiles, [...]:

  • get has the memory effects of reading a volatile variable.
  • set has the memory effects of writing (assigning) a volatile variable.
  • [...]
  • compareAndSet and all other read-and-update operations such as getAndIncrement have the memory effects of both reading and writing volatile variables.


原子的问题是什么 compareAndSet试图解决?为什么使用(例如) atomicInteger.compareAndSet(1,2)而不是 if(volatileInt == 1) { volatileInt = 2; } ?它并不试图解决并发读取的任何问题,因为这些问题已经由常规 volatile 处理了。 . (“ volatile ”读取或写入与“原子”读取或写入相同。并发读取仅在写入过程中发生,或者语句以某种有问题的方式重新排序或优化时才会出现问题;但是 volatile 已经阻止了这些事情。) compareAndSet 唯一的问题解决的是,在 volatileInt在我们读取 volatileInt 之间,其他一些线程可能会并发写入。 ( volatileInt == 1 ) 以及当我们写信给它时 ( volatileInt = 2 )。 compareAndSet通过在此期间锁定任何竞争写入来解决此问题。

在“更新程序”( AtomicReferenceFieldUpdater 等)的特定情况下同样如此: volatile阅读仍然只是桃子。更新者的 compareAndSet方法的唯一限制是,它们不会像我上面写的那样“锁定任何竞争写入”,而是仅锁定来自 AtomicReferenceFieldUpdater 的同一实例的竞争写入。 ;当您同时更新 volatile 时,它们无法保护您直接字段(或者,就此而言,当您同时使用多个 AtomicReferenceFieldUpdater 来更新相同的 volatile 字段时)。 (顺便说一下,这取决于你如何看待它—— AtomicReference 和它的同类也是如此:如果你以绕过他们自己的 setter 的方式更新他们的字段,他们就无法保护你。不同之处在于 AtomicReference 实际上拥有它的字段,它是 private ,所以没有必要警告你不要以某种方式通过外部手段修改它。)

所以,回答你的问题:是的,你可以继续阅读 volatile具有相同原子性的字段保证不会出现部分/不一致读取、语句被重新排序等。

编辑添加 (12 月 6 日):任何对此主题特别感兴趣的人都可能对下面的讨论感兴趣。我被要求更新答案以澄清该讨论中的要点:
  • 我认为最重要的一点是,以上是我自己对文档的解释。我相当有信心我已经正确理解了它,并且没有其他解释有意义;如果需要,我可以详细地争论这一点;-);但是我和其他任何人都没有引用任何权威文档来解决这一点,这比问题本身提到的两个文档(该类的 Javadoc 和 Java 并发实践)和我在原始答案中提到的一个文档更明确它在上面(包的 Javadoc)。
  • 我认为下一个最重要的一点是,尽管 AtomicReferenceUpdater 的文档说混合不安全compareAndSet使用 volatile 写入,我相信在典型平台上它实际上是安全的。只有在一般情况下才不安全。我这样说是因为包文档中的以下评论:

    The specifications of these methods enable implementations to employ efficient machine-level atomic instructions that are available on contemporary processors. However on some platforms, support may entail some form of internal locking. Thus the methods are not strictly guaranteed to be non-blocking -- a thread may block transiently before performing the operation.



    所以:
  • 在现代处理器的典型 JDK 实现中,AtomicReference.set简单地使用 volatile 写入,因为 AtomicReference.compareAndSet使用相对于 volatile 写入是原子的比较和交换操作。 AtomicReferenceUpdater.set肯定比 AtomicReference.set 更复杂,因为它必须使用类似反射的逻辑来更新另一个对象中的字段,但我认为这是它更复杂的唯一原因。一个典型的实现调用 Unsafe.putObjectVolatile ,这是一个较长名称的 volatile 写入。
  • 但并非所有平台都支持这种方法,如果不支持,则允许阻塞。冒着过于简单化的风险,我认为这大致意味着原子类的 compareAndSet可以通过(或多或少)应用 synchronized 来实现到使用 get 的方法和 set直截了当。但要让它起作用,set也必须是 synchronized ,原因在我上面的原始答案中解释过;也就是说,它不能只是一个 volatile 写入,因为它可以修改 compareAndSet 之后的字段。已调用 get但之前compareAndSet电话set .
  • 不用说,我的原始答案中使用的短语“锁定”不应该从字面上理解,因为在典型的平台上,不需要发生任何类似锁定的情况。
  • 在 Sun 的 JDK 1.6.0_05 实现中 java.util.concurrent.ConcurrentLinkedQueue<E> ,我们发现:
    private static class Node<E> {
    private volatile E item;
    private volatile Node<E> next;
    private static final AtomicReferenceFieldUpdater<Node, Node> nextUpdater =
    AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");
    private static final AtomicReferenceFieldUpdater<Node, Object> itemUpdater =
    AtomicReferenceFieldUpdater.newUpdater(Node.class, Object.class, "item");
    Node(E x) { item = x; }
    Node(E x, Node<E> n) { item = x; next = n; }
    E getItem() { return item; }
    boolean casItem(E cmp, E val)
    { return itemUpdater.compareAndSet(this, cmp, val); }
    void setItem(E val) { itemUpdater.set(this, val); }
    Node<E> getNext() { return next; }
    boolean casNext(Node<E> cmp, Node<E> val)
    { return nextUpdater.compareAndSet(this, cmp, val); }
    void setNext(Node<E> val) { nextUpdater.set(this, val); }
    }

    (注意:为紧凑性调整了空格),其中,一旦构造了实例,就没有 volatile 写入——也就是说,所有写入都是通过 AtomicReferenceFieldUpdater.compareAndSet 进行的。或 AtomicReferenceFieldUpdater.set — 但 volatile 读取似乎可以自由使用,无需调用 AtomicReferenceFieldUpdater.get . JDK 1.6 的更高版本改为使用 Unsafe直接(这发生在 Oracle 的 JDK 1.6.0_27 中),但是 JSR 166 邮件列表上的讨论将这种更改归因于性能考虑,而不是对先前实现的正确性的任何疑虑。
  • 但我必须指出,这不是防弹权威。为方便起见,我在写“Sun 的实现”时好像它是一个单一的东西,但我之前的要点明确指出,不同平台的 JDK 实现可能必须以不同的方式做事。上面的代码在我看来是以一种平台中立的方式编写的,因为它避开了普通的 volatile 写入而支持对 AtomicReferenceFieldUpdater.set 的调用。 ;但是不接受我对这一点的解释的人可能不会接受我对另一点的解释,并且可能会争辩说上述代码并不意味着对所有平台都是安全的。
  • 此权限的另一个弱点是,尽管 Node似乎允许 volatile 读取与对 AtomicReferenceFieldUpdater.compareAndSet 的调用同时发生,这是一个私有(private)类(class);而且我没有提供任何证据证明其所有者( ConcurrentLinkedQueue )实际上在没有自己的预防措施的情况下进行了此类调用。 (虽然我还没有证明这个说法,但我怀疑有人会对此提出异议。)

  • 有关本附录的背景和进一步讨论,请参阅以下评论。

    关于java - AtomicReferenceFieldUpdater - 方法 set、get、compareAndSet 语义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8262982/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com