It's actually fairly hard to precisely map the JMM to barriers. The job for (some of us) runtime implementors it fairly easy, as there are clear rules we can follow that while overly conservative, are **sufficient** for meeting the memory model requirements. You can find those in Doug Lea's excellent JMM cookbook (http://gee.cs.oswego.edu/dl/jmm/cookbook.html), and you'll see a matrix there that explain the sort of LoadLoad | LoadStore | StoreStore | StoreLoad combos would be sufficient to place between various operations for the memory model to be adhered to. However, it is critical to understand that these are *not rules that *you* can rely* on as a programmer. The cookbook is written for runtime and compiler implementors and provide an "if you follow this you are safe in your implementation" set, but runtimes are allowed to be more aggressive than the rules specified and still meet the JMM requirements. And they do. So programs that run on those runtimes are not guaranteed anything in the cookbook. E.g. lock coarsening is an optimization that is more aggressive than the cookbook rules, but is allowed by the JMM. And so is lock biasing. And so is the ability to eliminate monitor and/or volatile operations on provably-thread-local objects and fields, e.g. when escape analysis can show that no other thread will ever observe a given object.
A specific quality that the JMM has that is "looser" than the notion of fences or barriers is that the rules apply to specific variables and not globally to all memory operations. While fences and barriers are usually described in a global sense (a rule that applies to all pervious/subsequent stores or loads), the rules of the JMM only apply to operations in other threads that interact with the same volatile variable or monitor in question. E.g. with regards to other threads operations on the same volatile variable, a volatile read will appear to have the equivalent of a LoadLoad | LoadSore between the volatile read operation any any subsequent loads or stores (seen in program order in the thread). But this promise does not apply against other threads that do not interact with the same volatile. So e.g. if the volatile can be proven to be thread local, a volatile read has no ordering implications. The same is true for volatile stores, so a volatile store will appear to have a LoadStore|StoreStore fence between any preceding loads and store of the volatile store operation when considered from the point of view of another thread operating on the same volatile field. But it cannot be relied on to create a global StoreStore ro LoadStore fence, or the equivalent of an acquire or release, since it can be optimized away under certain conditions (like e.g. if the field was a member of an an object that was proven to be thread-local via escape analysis and therefore is now to not have any other threads interacts with it). The same caveats apply to monitor enter and monitor exist. On Tuesday, July 5, 2016 at 2:53:43 PM UTC-7, Dain Ironfoot wrote: > > Gil thanks for the wonderfully simple yet clear explanation on the > barriers. > > May I ask you to please help me understand the related "memory visibility" > guarantees that these barriers provide in JMM. > AFAIK, in java, we have the following language level instructions which > deals with memory visibility & ordering. > > volatile read (Atomicxxx get) > volatile write (Atomicxxx set) > > Atomicxxx lazySet (Unsafe.putOrdered*) > lazySet is not described or related to in any way in the current (Java SE 8 or prior) JMM. In implementation, it is usually equivalent to a StoreStore fence preceding the set operation. This quality seems to be relied on by a lot of concurrent Java code (including code within the JDK), as it is a relatively cheap barrier. > > Unsafe.loadFence() > Unsafe.storeFence() > Unsafe.fullFence() > Similarly not defined in the JMM. And (like everything else in Unsafe) not defined by the Java SE spec. But described in JEP 171 <http://openjdk.java.net/jeps/171> in a way that would make them equivalent to: Unsafe.loadFence() : LoadLoad | LoadStore Unsafe.storeFence(): StoreLoad | StoreStore Unsafe.fullFence(): LoadLoad | LoadStore | StoreLoad | StoreStore > > Lock acquire > Lock release > I assume you are referring to j.u.c Locks here. According to the j.u.c.locks.Lock <https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Lock.html#lock()> JavaDoc, those have the same semantics as monitor enter and monitor exist. > final fields in the constructor > This is defined in the JMM. With regards to other threads that may observe the contents of the final field, it can be thought of loosely as having a StoreStore fence between the final field initializer and any subsequent store operation that would make the object that contains the final field visible to other threads (e.g. storing it's reference into a heap object). But keep in mind the same JMM loosening possibility: if the field can be proven to not be visible to other threads (e.g. via escape analysis) then there is no ordering required. So depending on the runtime, it may not exist. > > Given your model of "LoadLoad", "LoadStore", "StoreStore", "StoreLoad"; is > it helpful to map them to these instructions above? > Also, how do you think about the memory visibility that are provided by > these instructions? > > Many thanks > > >>> -- You received this message because you are subscribed to the Google Groups "mechanical-sympathy" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. For more options, visit https://groups.google.com/d/optout.
