This is part 2 of my notes from reading Java Concurrency in Practice.
NOTE: These summaries are NOT meant to replace the book. I highly recommend buying your own copy of the book if you haven't already read it.
Chapter 3 - Sharing Objects
- Synchronization (for example, by using synchronized blocks) is not just about atomic execution of code blocks, it also influences memory visibility - i.e., ensures that a thread can see the changes made in another thread. Without synchronization, the Java memory model does not guarantee that a value written by a thread will be seen by another thread on a timely basis or even at all. For example, if proper synchronization is not used, a thread X that relies on a control variable that is set in thread Y may NEVER see any updates to it that are written in thread Y. In most cases, thread X will incorrectly loop for ever.
- If synchronization is not used, reordering of operations done by multi-processor CPUs for improving performance, can cause a thread to see an incorrect or partial value written by another thread. Without synchronization, the data can be stale. If thread first sets variable x to 1 and then y to 2, another thread may see y set to 2 while x is still unset. This can lead to bugs that are very hard to debug.
- Always use synchronization whenever data is shared across threads.
- Out-of-thin-air safety - A thread always sees the value of a variable that was written by some thread; not some random value pulled out of thin air. Unless declared as volatile, 64-bit numeric variables (long and double) do not have out-of-thin-air safety, because the JVM treats 64-bit operations as two 32-bit operations.
- Volatile variables provide a weaker form of synchronization.
- Volatile variables are specially treated by the compiler (for eg: not cached in registers). So a read of a volatile variable always returns the latest value written by some thread.
- When thread A writes to a volatile variable and subsequently thread B reads that same variable, the values of all variables visible to A prior to writing the volatile variable become visible to B after reading the volatile variable.
- Don't overuse volatile, and in tricky ways. Synchronized blocks are still necessary for atomic operations.
- Volatile is commonly used for a control variable that determines when a thread should exit an infinite loop.
- Locking guarantees visibility and atomicity; volatile variables guarantee only visibility.
- Use volatile variables only when all the following conditions are satisfied:
- Writes to the variable do not depend on its current value, or if it is guaranteed that only a single thread writes to the variable.
- The variable does not participate in invariants with other state variables.
- Locking is not required for any other reason while the variable is being accessed.
- For server applications, always specify the -server JVM command line argument even while developing and testing, since the JVM does more drastic optimizations in server mode. Some concurrency bugs arise only under these optimizations.
- Publishing an object means making it available to code outside of its current scope.
- This can be done by:
- storing a reference to it somewhere where other code can find it, say a public static field or in a publicly accessible HashMap.
- returning it from a non-private method.
- passing it to an alien method
- a method in other classes
- an overridable method in the same class
- publishing an inner class instance (this automatically exposes the enclosing instance)
- Sometimes we do not want to publish an object since that will break encapsulation. An object that is published when it should not have been is said to have escaped.
- Do not allow the this reference to escape during construction. This commonly happens when the constructor registers some inner class with external event listeners or starts a thread. Even if this is the last statement in the constructor, it is possible that a reference to the object may escape before it is fully constructed. Other threads can see the partially constructed object and react incorrectly. Use a separate start() method to start a thread created in the constructor, or to register event listeners created in the constructor. Alternatively, to do it one step, use a newInstance() factory method that calls the constructor and then automatically calls start() before returning the newly created object.
- Calling an overriden instance method from the constructor also allows this to escape before being fully constructed.
- Thread confinement, i.e., make sure data is accessed only from one thread, is the easiest way to achieve thread safety.
- Swing UI framework & JDBC connection objects use thread confinement extensively.
- Thread confinement options:
- Ad-hoc thread confinement - programmer entirely responsible to confine object to thread - no language features used. Not recommended due to fragility.
- Stack confinement - Object can be reached only through local variables
- Primitive types are always stack confined.
- Care should be taken that object references do not escape.
- ThreadLocal - provides get and set methods that maintain a separate copy of a value for each thread. Used as: new ThreadLocal
() { public T initialValue() {...}} - Immutability - Immutable objects are always thread-safe.
- Even if all fields of an object are final, it may still not be immutable as some of its final fields can refer to mutable objects.
- Final fields provide initialization safety as they have special semantics under the Java Memory Model. Make all fields of a class final unless they really need to be mutable
- When a group of related data items must be processed atomically, consider creating an immutable holder class. When an immutable holder class, we may be able to avoid a synchronized block.
- Safe publication
- Simply storing a reference to an object into a public field is not safe, as it could lead to other threads seeing the object in a partially constructed state (due to reordering).
- Immutable objects can be published through any mechanism; no synchronization necessary.
- Others must be safely published, i.e., both the reference to the object and the object's state must be made visible to other threads at the same time. A properly constructed object can be safely published by:
- Initializing an object reference from a static initializer. This is often the easiest way; static initializers are executed by the JVM at class initialization time which has JVM-internal synchronization.
- Storing a reference to it in a 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, like thread-safe collections like Vector or synchronizedList.
- Effectively immutable objects must be safely published.
- Objects that are not technically immutable, but whose state will not be modified after publication are called effectively immutable. Safely published effectively immutable objects can be safely used by any thread without additional synchronization. For example, the Date object is often used as an effectively immutable object although it is technically mutable.
- Mutable objects must be safely published, AND must be either thread-safe or guarded by a lock.