Site Search:

Chapter 3 Sharing Objects

<Back

3.1 Visibility

In the absence of synchronization, the compiler, processor, and runtime can do some downright weird things to the order in which operations appear to execute. Attempts to reason about the order in which memory actions "must" happen in insufficiently synchronized multithreaded programs will almost certainly be incorrect.

Locking is not just about mutual exclusion; it is also about memory visibility. To ensure that all threads see the most up-to-date values of shared mutable variables, the reading and writing threads must synchronize on a common lock.

Use volatile variables only when they simplify implementing and verifying your synchronization policy; avoid using volatile variables when verifying correctness would require subtle reasoning about visibility. Good uses of volatile variables include ensuring the visibility of their own state, that of the object they refer to, or indicating that an important lifecycle event (such as initialization or shutdown) has occurred.

Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility.

The following example demonstrates visibility issue and stale data in multithreaded program. It also shows why this kind of bug is called heisenbug -- it disappear when observing. Finally a simple solution is provided with volatile variable.


NonVisibility.java

3.2 Publication and escape

Do not allow the this reference to escape during construction.

An object that is published when it should not have been is said to have escaped.

"A mechanism by which an object or its internal state can be published is to publish an inner class instance, as shown in Listing 3.7.  When ThisEscape publishes the EventListener, it implicitly publishes the enclosing ThisEscape instance as well, because inner class instances contain a hidden reference to the enclosing instance."

The following example demonstrate a reference of the anonymous class EventListener can be obtained in a subclass of ThisEscape. With the reference to EventListener, the ThisEscape object can be referenced through the inner class. When the inner class reference the private instance variables via this reference, the object may or may not be ready. Run the program most of time is fine, but sooner or later, we will run into the following exception.

Exception in thread "Thread-0" java.lang.NullPointerException: Cannot invoke "ThisEscape$EventListener.onEvent(ThisEscape$Event)" because "this.el" is null
at CopierEventSource.sendEvent(CopierEventSource.java:23)
at CopierEventSource$1.run(CopierEventSource.java:6)
at java.base/java.lang.Thread.run(Thread.java:832)


Listing 3.8 no longer unpredictable. Besides, SafeListener can not be subclassed, we can not override the doSomething to explore the this reference. SafeListenerRunner.java.

3.3 Thread confinement

Stack confinement is a special case of thread confinement in which an object can only be reached through local variables.
Now take a look at Listing 3.9, in loadTheArk, we instantiate a TreeSet and store a reference to it in animals. At this point, there is exactly one reference to the set, held in a local variable and therefore confined to the executing thread.


Animals.java single threaded version

Animals.java first attempt for multi-threading

Java concurrency example -- Load the ark with multithreading part 2 code

Java concurrency example -- Load the ark with multithreading part 3 code

Java concurrency example -- Load the ark with multithreading part 4 code


In the following example,  an instance of MyRunnable class is shared by two threads. The ThreadLocal varialbe makes sure each thread get its own copy of the variable.


ThreadLocalExample.java


3.4 Immutability

Immutable objects are always thread-safe.
An Object is immutable if:
  • Its State cannot be modified after construction.
  • All its fields are final; and
  • It is properly constructed (the this reference does not escape during construction).

3.5 Safe publication

Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them.

To publish an object safely, 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;
  • Storing a reference to it into 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.
The following example shows what could happen if a mutable object isn't safely published.

StuffIntoPublic.java

Safely published effectively immutable objects can be used safely by any thread without additional synchronization.

The publication requirements for an object depend on its mutability:
  • Immutable objects can be published through any mechanism;
  • Effectively immutable objects must be safely published;
  • Mutable objects must be safely published, and must be either thread safe or guarded by a lock.
The most useful policies for using and sharing objects in a concurrent program are:
  • Thread-confined -- A thread-confined object is owned exclusively by and confined to one thread, and can be modified by its owning thread.
  • Shared read-only -- A shared read-only object can be accessed concurrently by multiple threads without additional synchronization, but cannot be modified by any thread. Shared read-only objects include immutable and effectively immutable objects.
  • Shared thread-safe -- A thread-safe object performs synchronization internally, so multiple threads can freely access it through its public interface without further synchronization.
  • Guarded -- A guarded object can be accessed only with a specific lock held. Guarded objects include those that are encapsulated within other thread-safe objects and published objects that are known to be guarded by a specific lock.