AtomicReferenceFieldUpdater - 方法 set、get、compareAndSet 语义

发布于 2024-12-18 02:04:33 字数 1065 浏览 2 评论 0原文

来自 Java AtomicReferenceFieldUpdater文档

请注意,此类中的 compareAndSet 方法的保证是 比其他原子类弱。因为这个类不能保证 该字段的所有用途都适合原子目的 访问,它只能保证原子性和易失性语义 尊重 compareAndSetset 的其他调用。

这意味着我无法与 compareAndSet 一起进行正常的易失性写入,而必须使用 set 来代替。它没有提及任何有关 get 的内容。

这是否意味着我仍然可以读取具有相同原子性保证的易失性字段 - 在 setcompareAndSet 之前的所有写入对于读取易失性字段的每个人都是可见的?

或者我是否必须在 AtomicReferenceFieldUpdater 上使用 get 而不是字段上的易失性读取?

如果您有参考文献,请发布。

谢谢。

编辑:

来自 Java 并发实践,他们唯一说的是:

更新器类的原子性保证比 常规原子类,因为你不能保证 底层字段不会被直接修改——compareAndSet 和算术方法仅保证其他方面的原子性 使用原子字段更新器方法的线程。

同样,没有提及其他线程应该如何读取这些易失性字段。

另外,我是否正确地假设“直接修改”是常规的易失性写入?

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.

This means I can't do normal volatile writes along with compareAndSet, but have to use set instead. It doesn't mention anything about get.

Does that mean that I can still read volatile fields with the same atomicity guarantees - all writes before the set or compareAndSet are visible to everybody who has read the volatile field being?

Or do I have to use get on the AtomicReferenceFieldUpdater instead of volatile reads on the field?

Please post references if you have them.

Thank you.

EDIT:

From Java Concurrency in Practice, the only thing they say:

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.

Again, no mention of how the other threads are supposed to read these volatile fields.

Also, am I right to assume that "modified directly" is a regular volatile write?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

溺孤伤于心 2024-12-25 02:04:33

正如原子的包文档中所述(一般来说,不是特定的更新程序):

原子访问和更新的记忆效应通常遵循易失性规则,[...]:

  • get 具有读取 易失性 变量的记忆效应。
  • set 具有写入(分配)易失性 变量的记忆效应。
  • [...]
  • compareAndSet 以及所有其他读取和更新操作(例如 getAndIncrement)都具有读取和写入 易失性 变量的记忆效应。< /里>

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

在“更新程序”(AtomicReferenceFieldUpdater 等)的特定情况下也是如此:易失性 读取仍然很理想。更新程序的 compareAndSet 方法的唯一限制是,它们不是像我上面写的那样“锁定任何竞争写入”,而是仅锁定来自AtomicReferenceFieldUpdater 的同一实例;当您同时直接更新 volatile 字段时(或者,当您同时使用多个 AtomicReferenceFieldUpdater 来更新相同的字段时)它们无法保护您易失性字段)。 (顺便说一句,这取决于您如何看待它 - AtomicReference 及其同类也是如此:如果您以绕过其自己的 setter 的方式更新其字段,它们将无法保护您。不同之处在于 AtomicReference 实际上拥有其字段,并且它是 private,因此无需警告您不要通过外部方式对其进行修改。)

因此,回答你的问题:是的,你可以继续读取具有相同原子性保证的易失性字段,以防止部分/不一致读取、防止语句重新排序等。


编辑添加(12月6):任何对此主题特别感兴趣的人可能会对下面的讨论感兴趣。我被要求更新答案,以澄清该讨论中的要点:

  • 我认为要添加的最重要的一点是,上述内容是我自己对文档的解释。我相当有信心我已经正确理解了它,并且没有其他解释有意义;如果需要的话,我可以详细论证这一点;-);但我和其他任何人都没有提供任何权威文档的引用,比问题本身中提到的两个文档(该类的 Javadoc 和 Java Concurrency in Practice)和一个文档更明确地解决了这一点我在上面的原始答案中提到的文档(包的 Javadoc)。

  • 我认为下一个最重要的一点是,尽管 AtomicReferenceUpdater 的文档说将 compareAndSet 与易失性写入混合使用是不安全的,但我相信在典型情况下平台实际上是安全的。仅在一般情况下才是不安全的。我这样说是因为包文档中的以下评论:

    <块引用>

    这些方法的规范使实现能够采用当代处理器上可用的高效机器级原子指令。然而,在某些平台上,支持可能需要某种形式的内部锁定。因此,不能严格保证这些方法是非阻塞的——线程在执行操作之前可能会暂时阻塞。

    所以:

    • 在现代处理器的典型 JDK 实现中,AtomicReference.set 仅使用易失性写入,因为 AtomicReference.compareAndSet 使用比较和交换操作相对于易失性写入而言是原子的。 AtomicReferenceUpdater.set 必然比 AtomicReference.set 更复杂,因为它必须使用类似反射的逻辑来更新另一个对象中的字段,但我认为这就是只是因为它更复杂。典型的实现调用Unsafe.putObjectVolatile,这是一个较长名称的易失性写入。
    • 但并非所有平台都支持这种方法,如果不支持,则允许阻止。冒着过于简单化的风险,我认为这大致意味着原子类的compareAndSet可以通过(或多或少)将synchronized应用到使用的方法来实现直接获取和设置。但要使其发挥作用,set 还必须synchronized,原因在我上面的原始答案中已解释过;也就是说,它不能只是一个易失性写入,因为这样它就可以在 compareAndSet 调用 get 之后但在 compareAndSet 调用之前修改该字段设置
    • 不用说,我最初的答案中使用的“锁定”一词不应按字面意思理解,因为在典型的平台上不需要发生任何类似锁定的情况。
  • 在 Sun 的 JDK 1.6.0_05 的 java.util.concurrent.ConcurrentLinkedQueue 实现中,我们发现:

    私有静态类 Node; {
        私有易失E项;
        私有易失性节点下一个;
        私有静态最终 AtomicReferenceFieldUpdater下一个更新程序 =
            AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");
        private static final AtomicReferenceFieldUpdater;项目更新器 =
            AtomicReferenceFieldUpdater.newUpdater(Node.class, Object.class, "item");
        节点(E x) { item = x; }
        节点(E x, 节点 n) { item = x;下一个 = n; }
        E getItem() { 返回项目; }
        布尔 casItem(E cmp, E val)
            { return itemUpdater.compareAndSet(this, cmp, val); }
        void setItem(E val) { itemUpdater.set(this, val); }
        节点 getNext() { 返回下一个; }
        boolean casNext(Node cmp, Node val)
            { 返回 nextUpdater.compareAndSet(this, cmp, val); }
        void setNext(Node val) { nextUpdater.set(this, val); } }
    }
    

    (注意:为紧凑性调整了空格),其中,一旦构造了实例,就没有易失性写入 - 也就是说,所有写入都是通过 AtomicReferenceFieldUpdater.compareAndSetAtomicReferenceFieldUpdater 进行的。 set - 但易失性读取似乎可以自由使用,无需调用 AtomicReferenceFieldUpdater.get。 JDK 1.6 的后续版本更改为直接使用 Unsafe(Oracle 的 JDK 1.6.0_27 就发生了这种情况),但 JSR 166 邮件列表上的讨论将这一更改归因于性能考虑,而不是任何关于先前实现的正确性。

    • 但我必须指出,这并不是万无一失的权威。为了方便起见,我写“Sun 的实现”就好像它是一个统一的东西,但我之前的要点表明,不同平台的 JDK 实现可能必须以不同的方式做事。在我看来,上面的代码是以平台中立的方式编写的,因为它避开了普通的易失性写入,而是调用 AtomicReferenceFieldUpdater.set;但是不接受我对这一点的解释的人可能不会接受我对另一点的解释,并且可能会争辩说上面的代码意味着对所有平台都是安全的。
    • 此权限的另一个弱点是,尽管 Node 似乎允许与 AtomicReferenceFieldUpdater.compareAndSet 调用同时发生易失性读取,但它是一个私有类;并且我没有采取任何证据证明其所有者 (ConcurrentLinkedQueue) 实际上在没有采取任何预防措施的情况下进行此类调用。 (虽然我还没有证明这一说法,但我怀疑有人会对此提出异议。)

请参阅下面的评论以了解本附录的背景以及进一步的讨论。

As explained in the package documentation for atomics (in general, not the updaters specifically):

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.

What problem is an atomic's compareAndSet trying to solve? Why use (for example) atomicInteger.compareAndSet(1,2) instead of if(volatileInt == 1) { volatileInt = 2; }? It's not trying to solve any problem with concurrent reads, because those are already taken care of by a regular volatile. (A "volatile" read or write is the same as an "atomic" read or write. A concurrent read would only be a problem if it happened in the middle of a write, or if statements were reordered or optimized in some problematic way; but volatile already prevents those things.) The only problem that compareAndSet solves is that, in the volatileInt approach, some other thread might come in with a concurrent write, between when we read volatileInt (volatileInt == 1) and when we write to it (volatileInt = 2). compareAndSet solves this problem by locking out any competing writes during that time.

This is equally true in the specific case of the "updaters" (AtomicReferenceFieldUpdater etc.): volatile reads are still just peachy. The updaters' compareAndSet methods' only limitation is that, instead of "locking out any competing writes" as I wrote above, they only lock out competing writes from the same instance of AtomicReferenceFieldUpdater; they can't protect you when you're concurrently updating a volatile field directly (or, for that matter, when you're concurrently using multiple AtomicReferenceFieldUpdaters to update the same volatile field). (Incidentally, depending how you look at it — the same is true of AtomicReference and its kin: if you were to update their fields in a way that bypassed their own setters, they couldn't protect you. The difference is that an AtomicReference actually owns its field, and it's private, so there's no need to warn you against somehow modifying it by external means.)

So, to answer your question: Yes, you can continue to read volatile fields with the same atomicity guarantees against partial/inconsistent reads, against statements being reordered, etc.


Edited to add (Dec 6): Anyone who's particularly interested in this subject will probably be interested in the discussion immediately below. I was asked to update the answer to clarify salient points from that discussion:

  • I think the most important point to add is that the above is my own interpretation of the documentation. I'm fairly confident that I have understood it correctly, and that no other interpretation makes sense; and I can, if desired, argue the point at length ;-) ; but neither I nor anyone else has produced any references to any authoritative document that addresses this point any more explicitly than the two documents mentioned in the question itself (the class's Javadoc and Java Concurrency in Practice) and the one document mentioned in my original answer to it above (the package's Javadoc).

  • The next most important point, I think, is that although the documentation for AtomicReferenceUpdater says that it's unsafe to mix compareAndSet with a volatile write, I believe that on typical platforms it actually is safe. It's unsafe only in the general case. I say this because of the following comment from the package documentation:

    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.

    So:

    • In a typical JDK implementation for a modern processor, AtomicReference.set simply uses a volatile write, since AtomicReference.compareAndSet uses a compare-and-swap operation that is atomic with respect to volatile writes. AtomicReferenceUpdater.set is necessarily more complex than AtomicReference.set, because it has to use reflection-like logic to update a field in another object, but I maintain that that is the only reason it is more complex. A typical implementation calls Unsafe.putObjectVolatile, which is a volatile write by longer name.
    • But not all platforms support this approach, and if they don't, then blocking is permitted. At the risk of oversimplifying, I take this to mean roughly that an atomic class's compareAndSet could be implemented by (more or less) applying synchronized to a method that uses get and set straightforwardly. But for this to work, set must also be synchronized, for the reason explained in my original answer above; that is, it can't just be a volatile write, because then it could modify the field after compareAndSet has called get but before compareAndSet calls set.
    • Needless to say, my original answer's use of the phrase "locking out" shouldn't be taken literally, since on a typical platform nothing very lock-like need occur.
  • In Sun's JDK 1.6.0_05 implementation of java.util.concurrent.ConcurrentLinkedQueue<E>, we find this:

    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); }
    }
    

    (note: whitespace adjusted for compactness), where, once an instance has been constructed, there are no volatile writes — that is, all writes are via AtomicReferenceFieldUpdater.compareAndSet or AtomicReferenceFieldUpdater.set — but volatile reads appear to be used freely, without a single call to AtomicReferenceFieldUpdater.get. Later releases of JDK 1.6 were changed to use Unsafe directly (this had happened by Oracle's JDK 1.6.0_27), but discussions on the JSR 166 mailing list attribute this change to performance considerations rather than to any qualm about the correctness of the previous implementation.

    • But I must point out that this is not bullet-proof authority. For convenience, I write of "Sun's implementation" as though it had been a unitary thing, but my previous bullet-point makes obvious that JDK implementations for different platforms may have to do things differently. The above code seems to me to have been written in a platform-neutral way, since it eschews plain volatile writes in favor of calls to AtomicReferenceFieldUpdater.set; but someone who doesn't accept my interpretation of the one point may not accept my interpretation of the other, and might argue that the above code is not meant to be safe for all platforms.
    • Another weakness of this authority is that, although Node seems to allow volatile reads to take place concurrently with calls to AtomicReferenceFieldUpdater.compareAndSet, it's a private class; and I have not undertaken any proof that its owner (ConcurrentLinkedQueue) actually makes such calls without its own precautions. (But although I have not proven the claim, I doubt that anyone would dispute it.)

Please see the below comments for background on this addendum, and for further discussion.

绿萝 2024-12-25 02:04:33

这意味着对对象的引用将得到保证,但由于您可以使用任何对象,因此当另一个线程访问该对象时,该对象的字段可能无法正确写入。

唯一可以保证的方法是这些字段是最终的还是可变的。

What this means is that the reference to the object will be guaranteed but because you can use any object, the fields of that object may not be properly written when another thread goes to access the object.

The only way that could be guaranteed is if the fields were final or volatile.

留一抹残留的笑 2024-12-25 02:04:33

这不是问题的准确答案:

从文档中看,无论是解释还是意图都不清楚。如果这个想法是绕过允许它的架构上的全局排序(即易失性写入)[如 IBM Power 或 ARM],并且仅公开 CAS(LoadLinked/StoreCondition)行为而不进行防护,那么这将是一项相当惊人的工作,也是造成混乱的根源。

sun.misc.Unsafe 的 CAS 没有规范或顺序保证(之前发生过),但 java.util.atomic... 有。所以在较弱的模型 java.util.atomic impl 上。在这种情况下,需要必要的围栏来遵循 java 规范。

假设更新程序类实际上缺少栅栏。
如果他们这样做,字段的易失性读取(不使用 get)将返回更新值,即显然不需要 get() 。由于没有订购保证,以前的商店可能不会传播(在弱模型上)。在 x86/Sparc TSO 硬件上确保 java 规范。

然而,这也意味着 CAS 可以通过以下非易失性读取进行重新排序。
java.util.concurrent.SynchronousQueue 队列中有一个有趣的注释:

        // Note: item and mode fields don't need to be volatile
        // since they are always written before, and read after,
        // other volatile/atomic operations.

提到的所有原子操作都是 AtomicReferenceFieldUpdater 的 CAS。这意味着正常读取和写入与 AtomicReferenceFieldUpdater.CAS 之间缺少或重新排序,即行为类似于易失性写入。

        s.item = null;   // forget item
        s.waiter = null; // forget thread

        //....

        while ((p = head) != null && p != past && p.isCancelled())
            casHead(p, p.next);

只有 CAS,没有易失性写入。

鉴于上述条件,我得出的结论是 AtomicXXXFieldUpdater 公开了与其 AtomicXXX 对应项相同的语义。

This will not be an exact answer of the question:

Neither explanation, nor intent looks clear from the documentation. If the idea was to bypass global ordering aka volatile write on architectures that allow it [like IBM Power or ARM] and just expose CAS (LoadLinked/StoreCondition) behavior WITHOUT fencing, it'd quite an amazing effort and source of confusion.

sun.misc.Unsafe's CAS has no specification or ordering guarantees (known as happens before) but java.util.atomic... does. So on weaker model java.util.atomic impl. would require necessary fences to follow java specification in this case.

Assuming Updater classes actually lack the fences.
If they do so, volatile read of field (w/o using get) shall return the update value, i.e. clearly get() is unneeded. Since there won't be ordering guarantees, the previous stores might not be propagate (on weak models). On x86/Sparc TSO hardware ensures java spec.

However, that also means CAS can be reordered with following non-volatile reads.
There is an interesting note from java.util.concurrent.SynchronousQueue queue:

        // Note: item and mode fields don't need to be volatile
        // since they are always written before, and read after,
        // other volatile/atomic operations.

All atomic operation mentioned are exactly CAS of AtomicReferenceFieldUpdater. That would imply the lack or reording between normal reads AND writes and AtomicReferenceFieldUpdater.CAS, i.e. acting like volatile write.

        s.item = null;   // forget item
        s.waiter = null; // forget thread

        //....

        while ((p = head) != null && p != past && p.isCancelled())
            casHead(p, p.next);

Just CAS, no volatile writes.

Given the condition above, I'd conclude the AtomicXXXFieldUpdater expose the same semantics as their AtomicXXX counterparts.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文