Java Concurrency in Practice

Visibility [3.1]

public class NoVisibility {
    private static boolean ready;
    private static int number;
    private static class ReaderThread extends Thread {
        public void run() {
            while (!ready)
                Thread.yield();
            System.out.println(number);
        }
    }
    public static void main(String[] args) {
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}

Possible results:

  1. method prints 42 (expected),
  2. method never returns (value of ready is not visible to another thread - stale data),
  3. method prints 0 (assigning to number and ready is reordered).

When a thread reads a variable without synchronization, it may see a stale value, but at least it sees a value that was actually placed there by some thread rather than some random value. This safety guarantee is called out-of-thin-air safety.

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.

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

Immutability

An object is immutable if:

  1. Its state cannot be modified after construction,
  2. All its fields are final,
  3. It is properly constructed (the this reference does not escape during construction).

Safe Publication Idioms

A properly constructed object can be safely published by:

Publication requirements for objects:

Policies for using and sharing objects in concurrent programs:

Object can be instance confined - encapsulated within another object so that all code paths are known. Confinement makes it easier to build thread-safe classes because a class that confines its state can be analyzed for thread safety without having to examine the whole program.

Designing thread-safe classes

Design process:

You cannot ensure thread safety without understanding an object's invariants and postconditions. Constraints on the valid values or state transitions for state variables can create atomicity and encapsulation requirements.

State ownership

Ownership and encapsulation go together - object encapsulates the state that it owns and owns the state it encapsulates. A class usually does not own the objects passed to its methods or constructors, unless the method is designed to explicitly transfer ownership of objects passed in (such as the synchronized collection wrapper factory methods).

If a class is composed of multiple independent thread-safe state variables and has no operations that have any invalid state transitions, then it can delegate thread safety to the underlying state variables.

Concurrent collections

The producer-consumer pattern also enables several performance benefits. Producers and consumers can execute concurrently; if one is I/O-bound and the other is CPU-bound, executing them concurrently yields better overall throughput than executing them sequentially. If the producer and consumer activities are parallelizable to different degrees, tightly coupling them reduces parallelizability to that of the less parallelizable activity.

Producer-consumer pattern

The iterators returned by ConcurrentHashMap are weakly consistent instead of fail-fast. A weakly consistent iterator can tolerate concurrent modification, traverses elements as they existed when the iterator was constructed, and may (but is not guaranteed to) reflect modifications to the collection after the construction of the iterator.

Synchronizers

A synchronizer is any object that coordinates the control flow of threads based on its state. Blocking queues can act as synchronizers; other types of synchronizers include semaphores, barriers, and latches.

Summary of Part I

[Chapter 8.3 ]