AtomicReferenceFieldUpdater - methods set, get, compareAndSet semantics
Asked Answered
E

3

29

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?

Euxenite answered 24/11, 2011 at 22:17 Comment(4)
unless you use reflection to read/write the field, you can assume volatile read/write.Mather
Can you elaborate what you mean by "you can assume volatile read/write"? What does "assume" mean? Note that the API explicitly says you cannot do a volatile write and expect it to be atomic - you have to use a set method instead. The question is - do you also have to use a get method instead of volatile reads to ensure atomicity?Euxenite
Writes to references are atomic either way w/ or w/o volatile. The only atomic operation is the CAS since it's read->compare->modify. Read and write are always atomic, i.e. you can get half-valid reference. Volatile read ensures read-read barrier w/ regard to the previous reads, so in no shape or form you need to use especially get.Mather
If you wish to see an example look at the source of look at the source code of ConcurrentSkipListMap, AtomicReferenceFieldUpdarer is used ONLY for the CAS of the head, reads are just normal volatile reads. CAS is the only useful operation it offers, the rest are fillers.Mather
L
25

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.

Loge answered 29/11, 2011 at 23:12 Comment(17)
Thank you. Can you provide a reference for the paragraph on the updaters?Euxenite
@axel22: The passage that I quoted applies to all atomics, including the updaters. The rest of my answer was explaining what's in the documentation you've already read, and explaining why it is the way that it is. I take it that you'd like an authoritative reference that explicitly says that compareAndSet + volatile-read, specifically, is safe? Sadly, I don't have one, sorry. I think that my answer is the only way to interpret the documentation you've seen; but I have to admit that it is an interpretation. If you aren't convinced, I can offer more arguments, but not more references. Sorry.Loge
@axel22: Another "source" that may be of interest is the source-code of ConcurrentLinkedQueue, as "written by Doug Lea and Martin Buchholz with assistance from members of JCP JSR-166 Expert Group and released to the public domain", in Java 5: it uses AtomicReferenceFieldUpdater.set(...) and .compareAndSet(...), but for gets it just reads the field. (In Java 6 and 7, for performance reasons, it's been changed to use Unsafe directly, rather than AtomicReferenceFieldUpdater, so is no longer strictly relevant; but there, too, you see that it uses .putOrderedObject() andLoge
.compareAndSwapObject(), and does not bother with .getOrderedObject(), because there isn't one, because volatile is sufficient.)Loge
Ordinary AtomicXXX classes implements set() as just value = newValue. And even if I access AtomicXXX.value field via Black Magic Of Reflection (I can, sure) -- reflection-based write to that field still be a volatile write -- since AtomicXXX.value itself is volatile, and reflection does not cancel effects of volatile modifiers. So, it seems for me, there should be no difference between AtomicXXX.CAS/set pair interoperation, and AtomicUpdater.CAS/set/direct field access interoperation.Kweichow
@BegemoT: As I understand it, "Ordinary AtomicXXX classes implements set() as just value = newValue" is not exactly true. As the documentation explains, "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 on many platforms, atomic set() can just be value = newValue, but onLoge
some platforms, such an implementation would not be possible, because some amount of locking is required to implement compareAndSet in a way that's consistent with the Java Memory Model.Loge
@ruakh: Yes, it seems to be a legal interpretation. But AtomicXXX.set() has exactly the same semantics as just volatile store. So, it seems for me what if some locking required for implementing A.set(), the same is true for just simple volatile store. And so, again, it is not obvious what is the differenceKweichow
@BegemoT: The difference is the existence of compareAndSet (and other read-plus-update operations). A volatile write offers various guarantees with respect to volatile reads and other volatile writes; an atomic write offers the same guarantees with respect to atomic reads, other atomic writes, and atomic read-plus-update operations. (Imagine you had to implement atomics using only volatile and synchronized. Your compareAndSet and set would both have to be synchronized, or else set could perform a volatile write between compareAndSet's volatile read and its volatile write.)Loge
@Loge Ok, now I've got it. In case of lack of hardware support for atomic RMW operations you've forced to differentiate simple 'volatiles' and 'atomics', since they are completely different in implementation. And forcing them share same (lock-based) implementation can made simple volatiles very inefficient -- which is not appropriate. Just a question left about how "official" are this interpretation? It seems what 1) It is very unusual for nowdays hardware to no have CAS-es 2) I've seen many examples of *Updater.CAS interoperate with plain volatiles. Even in jdk, I think...Kweichow
@BegemoT: This is my interpretation. As I said above, I think it's the only interpretation -- it seems to me that there's no other coherent way to read the docs, and other things I've read seem to be in accordance with it (e.g. certain postings to the JSR166 mailing-list) -- but I can't promise its "official"-ness. As for the JDK: I've seen -- and, above, referred to -- cases where the JDK mixes __Updater.CAS with volatile reads (which AFAICT is safe), but none where it mixes __Updater.CAS with volatile writes (which is documented to be unsafe in the general case).Loge
Of all the answers, this one makes most sense. A reference to the source code of some concurrent data structures the implementation of which confirms these claims would be great.Euxenite
@Loge Yes, thank you for your explanation -- now it seems pretty legal and, indeed, it seems like the only interpretation which consistently explain the reasoning behind javadoc being discussed. May I ask you to update your original answer, to clearify some points we've duscussed here, in comments ? It seems like it does not clear enough for first sight. I suppose, addind example with atomics implementation on CAS-missed hardware can greatly clearify the reasoning. And I can upvote your answer after editing :)Kweichow
Agreed - it would be ideal if the information in the comments would be merged into the answer itself.Euxenite
@Kweichow and axel22: O.K., I've given it a shot. The addendum is more than twice as long as the original answer, so it's not a very good shot, but it's something. :-P . . . anyway, needless to say, I welcome any further comments.Loge
@Loge I've posted the question in concurrency-insterest group, and it seems like your opinion got authoritive support: The user is correct. On implementations that require locks, none of the field updater classes can guarantee atomicity except with regards to other methods of the field updater. In practice I'm only aware of AtomicLongFieldUpdater actually having a lock-based implementation. David HolmesKweichow
Great answer, it covers pretty much everything.Intersect
M
2

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.

Mather answered 24/11, 2011 at 22:17 Comment(0)
J
2

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.

Jag answered 25/11, 2011 at 0:25 Comment(2)
In other words, the set in the atomic reference updater gives guarantees only on the particular field being modified? It's not like a normal volatile write? Note that the documentation does not say "...it can guarantee atomicity and volatile semantics only with respect to other invocations of compareAndSet and set invoked on the same field". There is the weakCompareAndSet for that.Euxenite
Also, these fields have to be volatile in the first place in order to be used with atomic field updaters.Euxenite

© 2022 - 2024 — McMap. All rights reserved.