The Java Memory Model What is it and why would I want one? Jörg Domaschka. ART Group, Institute for Distributed Systems Ulm University, Germany December 14, 2009
public class WhatDoIPrint{ static int x = 0, y = 0, a = 0, b = 0; public static void main(string[] args){ Thread one = new Thread(new Runnable(){ public void run(){ a = 1; x = b; } )}; Thread other = new Thread(new Runnable(){ public void run(){ b = 1; y = a; } )}; } one.start(); other.start(); one.join(); other.join(); System.out.println( ( + x +, + y + ) );
Possible Results Obviously: (1,0), (1,1), (0,1) Surprisingly: (0,0) Why that? Code reordering x = b (0) reorder a = 1 b = 1 y = a (0)
Reorderings Every entity optimises heavily Compiler Processor Caches In our case No shared variables declared Optimise for local operation
Outline Motivation JMM in a Nutshell The final Keyword Conclusions
Definitions Memory Model: Specifies when actions of one thread on memory are guaranteed to be visible by others. Defines: What you can expect from the system Operations for additional support Used in Multi-core/cpu architecture Multi-level caching
Java Memory Model Minimal guarantees the JVM must make about when writes to variables become visible. predictability and ease of programming vs high-performance What can we expect? Which additional operations do we have?
What to Expect? Sequential Consistency Mental model of many programmers A happy, if unrealistic model [Goetz] The von Neumann model is only a vague approximation [Goetz]
What to Expect? In the absence of special operations: Java provides barely any inter-thread guarantees Execution of a single thread is as-if sequential Don t expect anything Unless you prepared for it
Special Operations? In JMM Threads execute actions Actions are partially ordered (happens-before: antisymmetric, reflexive, transitive) Default: actions of different threads are not ordered
Special Actions! Inter-thread ordering actions Lock/unlock on monitor Volatile variables Thread start/termination/interruption... Synchronize all caches with main memory (Piggybacking on synchronization is possible) Define a synchronizes-with relation
Synchronisation Actions Totally ordered With respect to a single mutex/volatile variable No ordering guarantees for distinct variables Synchronized programmes Data race: Conflicting access to a shared variable that is not synchronized Correctly synchronized: All sequentially consistent executions are free of data races
Correct or Not? Thread 1: Thread 2: X = 1; Lock M1; Y = 1; Unlock M1; Lock M1; i = Y; Unlock M1; j = X;
Correct or Not? Thread 1: X = 1 Thread 2: Lock M1 Lock M1 i = Y Y = 1 Unlock M1 Unlock M1 j = X
Correct or Not? Thread 1: X = 1 Thread 2: Lock M1 Lock M1 i = Y Y = 1 Unlock M1 Unlock M1 j = X
Best Practices - Locking Every shared, mutable variable Guarded by exactly one lock Invariants that involve multiple variables All variables must be guarded by the same lock Resist the temptation to prematurely sacrifice simplicity for the sake of performance.
Best Pratices - volatile Volatile variables can only guarantee visibility. Writes must not depend on current value Does not participate in invariants Locking is not required for other reasons
Outline Motivation JMM in a Nutshell The final Keyword Conclusions
The final Keyword Semantics of final Fields Initialised once Never changed under normal circumstances Allows for compiler optimisations Do not depend on memory barriers (i.e., locks) Can always be cached Benefit: thread-safe immutable objects
final s Effect on Memory Freezing final fields At end of constructor But not before Reading a final field by constructing thread before freeze yields default value Allows building immutable objects Vital to Java security architecture Strings must not change
final s Visibility Guarantees After freeze of Object o All threads see correct value of final fields and fields exclusively reachable through them if they obtained reference to o after the freeze Non-final fields Only guaranteed to bee seen correctly by finalizer Unless synchronisation is used Requires safe publication of objects
Example class FinalFieldExample { final int x; int y; static FinalFieldExample f; public FinalFieldExample(){ x = 3; y = 4; } } static void reader(){ if (f!= null){ int i = f.x; int j = f.y; } } static void writer(){ f = new FinalFieldExample(); }
Example - Publication class UnsafeLazyInitialisation{ private static Resource resouce; } public static Resource getinstance(){ if(resource == null) resource = new Resource(); return resource; }
Example Publication (II) class SafeLazyInitialisation{ private static Resource resouce; } public synchronized static Resource getinstance(){ if(resource == null) resource = new Resource(); return resource; }
Example Publication (III) class EagerInitialisation{ private static Resource resouce = new Resource(); } public synchronized static Resource getinstance(){ return resource; }
Example Publication (IV) class ResourceFactory{ private static class ResourceHolder { public static Resource resource = new Resource(); } public synchronized static Resource getinstance(){ return ResourceHolder.resource; } }
Example Publication (V) pulic Holder holder; public void initialise(){ holder = new Holder(42); } public class Holder { private int n; public Holder(int i) { n = i; } } public void assertsanity(){ assert n == n : This statement is false ; }
Best Practices Sharing Objects Consider local variables/object No concurrency possible Consider using ThreadLocal One value per thread Use immutable objects where possible Immutable objects are always thread-safe If published correctly Make use of final Effect on visibility Easier reasoning about correctness
Best Practices - Publication Do not allow this references to escape during construction Consider using factory methods Do not start a thread from a constructor Use inheritance judiciously Publish safely
Best Practices Safe Publication Publish object reference and object state at the same time Static initialiser Write to volatile field or AtomicReference Store reference into a final field Store reference to a field guarded by a lock Placing an object in a unsynchronized Collection does not work in general
Outline Motivation Definitions JMM in a Nutshell The final Keyword Conclusions
Beyond Synchronisation JMM also defines Which reads/writes have to be atomic Semantics of wait and notify operations Parallel to interrupts Neither interrupt nor notify must get lost Semantics of sleep and yield Do not have any synchronisation semantics Semantics of modifying final fields
Conclusions JMM specifies when actions of one thread are guaranteed to be visible to another Threads run isolated: no guarantees Synchronisation actions provide inter-thread dependencies
Conclusions (II) final fields provide stronger guarantees (freeze when constructor finishes) Publish safely Locking can guarantee both: visibility and atomicity; volatile variables can only guarantee visibility.
References Goetz, Brian: Java Concurrency in Practice Bloch, Joshua: Effective Java Angelika Langer: Overview of the Java Memory Model Gosling, James et al.: The Java Language Specification, 3rd edition.
Modifying final fields Modification possible Using reflection Triggers an immediate freeze Sometimes required Executors, std{in,out,err} handlers Yet, discuraged Require additional compiler knowledge Counter-intuitive
Outline Motivation Definitions JMM in a Nutshell The final Keyword Best Practices Diving in Conclusions
Best Pratices Locking is not just about mutual exclusion; it is also about memory visibility. To ensure that all thread see the most up-to-date values of shared mutable variables, the reading and writing threads must synchronise on the same lock. Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility.
Diving In