Intrinsic Locking Revisited

Intrinsic locking provides a blocking, lock-based, thread-safe solution for concurrent threads accessing mutable shared resources. The keyword synchronized is used to implement critical sections of code (synchronized methods/blocks) that rely on intrinsic locks to guarantee mutual exclusion to a single thread at a time (§22.4, p. 1387). Race conditions are thus avoided by mutual exclusion guaranteed by the critical section. The happens-before object lock rule (in this case for intrinsic locks) guarantees that any updates to the shared data done by one thread in a critical section will be visible to any other thread in another critical section guarded by the same intrinsic lock—thus eliminating memory consistency errors (§22.5, p. 1414).

Example 23.11 is an implementation of a thread-safe counter using synchronized methods for this particular case. Note that it is not necessary to declare the counter field as volatile, as both visibility and absence of race conditions are guaranteed by the synchronized code. The two operations on the counter are implemented as synchronized methods that are mutually exclusive, ensuring thread-safe use of the counter when accessed by different threads.

Example 23.11 Synchronized Counter

Click here to view code image

package safe;
public class SynchronizedCounter implements ICounter  {
  private int counter = 0;
  @Override public synchronized int getValue() { return counter; }
  @Override public synchronized void increment() { counter++; }
}

Output from the program in Example 23.7, p. 1451:

Click here to view code image

Synchronized Counter:              10000

Programmatic Locking

The Lock API in the java.util.concurrent.locks package provides flexible programmatic locking mechanisms that can be used to ensure thread-safety of shared mutable resources accessed by concurrent threads.

Intrinsic locking is based on the acquire-release lock paradigm to implement mutual exclusion. This paradigm is also the basis of locking mechanisms for mutual exclusion provided by the Lock API. This API also allows more control over lock acquisition (i.e., how the lock should be acquired) and lock disciplines (i.e., how to choose a thread among the waiting threads to acquire a released lock). Programmatic locking is thus more flexible, and can prevent threads from potential resource starvation (§22.5, p. 1412).

Selected interfaces and classes from the Lock API are shown in Figure 23.3. The main interfaces are Lock and ReadWriteLock that are implemented by the ReentrantLock and the ReentrantReadWriteLock classes, respectively. The ReentrantReadWriteLock class defines two nested classes, ReadLock and WriteLock, that implement the Lock interface. We explore the Lock API in the next section.

Figure 23.3 Selected Interfaces and Classes in the Lock API