Search code examples
javaconcurrencyatomic

AtomicXXX.lazySet(...) in terms of happens before edges


What does mean AtomicXXX.lazySet(value) method in terms of happens-before edges, used in most of JMM reasoning? The javadocs is pure on it, and Sun bug 6275329 states:

The semantics are that the write is guaranteed not to be re-ordered with any previous write, but may be reordered with subsequent operations (or equivalently, might not be visible to other threads) until some other volatile write or synchronizing action occurs).

But this not a reasoning about HB edges, so it confuses me. Does it mean what lazySet() semantics can't be expressed in terms of HB edges?

UPDATE: I'll try to concretize my question. I can use ordinary volatile field in following scenario:

//thread 1: producer
...fill some data structure
myVolatileFlag = 1;

//thread 2: consumer
while(myVolatileFlag!=1){
   //spin-wait
}
...use data structure...

In this scenario use of "data structure" in consumer is correct, since volatile flag write-read make HB edge, giving guarantee what all writes to "data structure" by producer will be completed, and visible by consumer. But what if I'll use AtomicInteger.lazySet/get instead of volatile write/read in this scenario?

//thread 1: producer
...fill some data structure
myAtomicFlag.lazySet(1);

//thread 2: consumer
while(myAtomicFlag.get()!=1){
   //spin-wait
}
...use data structure...

will it be still correct? Can I still really on "data structure" values visibility in consumer thread?

It is not "from air" question -- I've seen such method in LMAX Disruptor code in exactly this scenario, and I don't understand how to prove it is correct...


Solution

  • The lazySet operations do not create happens-before edges and are therefore not guaranteed to be immediately visible. This is a low-level optimization that has only a few use-cases, which are mostly in concurrent data structures.

    The garbage collection example of nulling out linked list pointers has no user-visible side effects. The nulling is preferred so that if nodes in the list are in different generations, it doesn't force a more expensive collection to be performed to discard the link chain. The use of lazySet maintains hygenic semantics without incurring volatile write overhead.

    Another example is the usage of volatile fields guarded by a lock, such as in ConcurrentHashMap. The fields are volatile to allow lock-free reads, but writes must be performed under a lock to ensure strict consistency. As the lock guarantees the happens-before edge on release, an optimization is to use lazySet when writing to the fields and flushing all of the updates when unlocking. This helps keep the critical section short by avoiding unnecessary stalls and bus traffic.

    If you write a concurrent data structure then lazySet is a good trick to be aware of. Its a low-level optimization so its only worth considering when performance tuning.