Java Memory Model

  • aka JMM
  • 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
  • https://stackoverflow.com/questions/34524969/concurrent-access-to-a-public-field-why-is-it-possible-to-observe-inconsistent
  • Example
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

Piggybacking on synchronization