JMM specifies when the actions of one thread on memory are guaranteed to be visible to another
The specifics involve ensuring that operations are ordered by a partial ordering called happens-before, which is specified at the level of individual memory and synchronization operations
the higher-level rules such as @GuardedBy and safe publication, can be used to ensure thread safety without resorting to the low-level details of happens-before
Important happens-before rules
Each action in a thread happens-before every action in that thread that comes later in the program order
An unlock on a monitor or explicit lock happens-before every subsequent lock on that same monitor or explicit lock
A write to a volatile or atomic field happens-before every subsequent read of that same field
The end of a constructor for an object happens-before the start of the finalizer for that object
Synchronizers like CountDownLatch, Semaphore, CyclicBarrier, Future etc. also follows happens-before relationship b/w write and read
If A happens-before B, and B happens-before C, then A happens-before C
Properly Constructed
If the this reference does not escape during construction, the object is considered to be properly constructed
If this escapes during construction, then another thread might see incorrect state of the object while being constructed
Example when this reference escapes during construction
starting thread in constructor
register event listener in constructor
Safe Publication
To publish an object safely both reference to the object and object’s state must be made visible to other threads at the same time
With the exception of immutable objects, it is not safe to use an object that has been initialized by another thread unless the publication happens-before the consuming thread uses it
A properly constructed object can be safely published by any of the following:
Initializing an object reference from a static initializer
Storing reference to it into volatile field or AtomicReference
Storing a reference to it into a final field of a properly constructed object
Storing a reference to it into a field that is properly guarded by a lock
Example:
BlockingQueue implementations have sufficient internal synchronization to ensure that the put()happens-before the take()
Using a shared variable guarded by a lock or a shared volatile variable ensures that reads and writes of that variable are ordered by happens-before
Mutable and Immutable:
Immutable objects can be published using any publishing mechanism
Effectively immutable objects need to be safely published
Mutable objects need to be safely published & access to them need to be synchronized
Safe Initialization
Initialization safety makes visibility guarantees only for the values that are reachable through final fields as of the time the constructor finishes
For values reachable through non-final fields, or values that may change after construction, you must use synchronization to ensure visibility
Initialization safety guarantees that for properly constructed objects, all threads will see the correct values of final fields that were set by the constructor, regardless of how the object is published
Further, any variables that can be reached through a final field of a properly constructed object (such as the elements of a final array or the contents of a HashMap referenced by a final field) are also guaranteed to be visible to other threads
The guarantee of initialization safety allows properly constructed immutable objects to be safely shared across threads without synchronization, regardless of how they are published—even if published using a data race
public class Holder { private int n; public Holder(int n) { this.n = n; } public void assertSanity() { if (n != n) { // possible to execute since `n` is not `final` throw new AssertionError("This statement is false."); } }}
Static Initializer
The treatment of static fields with initializers (or fields whose value is initialized in a static initialization block [JPL 2.2.1 and 2.5.3]) is somewhat special and offers additional thread-safety guarantees
Static initializers are run by the JVM at class initialization time, after class loading but before the class is used by any thread
Because the JVM acquires a lock during initialization [JLS 12.4.2] and this lock is acquired by each thread at least once to ensure that the class has been loaded, memory writes made during static initialization are automatically visible to all threads
Thus statically initialized objects require no explicit synchronization either during construction or when being referenced
Example:
We can use eager-initialization using static initializer and make the class thread-safe
For lazy initialization singleton pattern, we can use holder class pattern and thread safety is guaranteed