Thread Synchronization

Throughout its execution, each thread is isolated from other threads because it has been given its own method-call stack. However, threads can still interfere with each other when they access and manipulate shared data. This interference can corrupt the shared data, and this corruption can cause an application to fail.

For example, consider a checking account in which a husband and wife have joint access. Suppose that the husband and wife decide to empty this account at the same time without knowing that the other is doing the same thing. Listing 7-8 demonstrates this scenario.

Listing 7-8. A problematic checking account public class CheckingAccount {

private int balance;

public CheckingAccount(int initialBalance) {

balance = initialBalance;

public boolean withdraw(int amount) {

catch (InterruptedException ie) {

balance -= amount;

return true;

return false;

public static void main(String[] args) {

final CheckingAccount ca = new CheckingAccount(100);

String name = Thread.currentThread().getName(); for (int i = 0; i < 10; i++)

System.out.println (name + " withdraws $10: " + ca.withdraw(10));

Thread thdHusband = new Thread(r);

thdHusband.setName("Husband");

Thread thdWife = new Thread(r);

thdWife.setName("Wife");

thdHusband.start();

thdWife.start();

This application lets more money be withdrawn than is available in the account. For example, the following output reveals $110 being withdrawn when only $100 is available:

Wife withdraws $10: true

Wife withdraws $10: true Husband withdraws $10: true Wife withdraws $10: true Husband withdraws $10: true Wife withdraws $10: true

Husband

withdraws

$10:

true

Husband

withdraws

$10:

true

Husband

withdraws

$10:

true

Husband

withdraws

$10:

true

Husband

withdraws

$10:

false

Husband

withdraws

$10:

false

Husband

withdraws

$10:

false

Husband

withdraws

$10:

false

Wife withdraws $10: true Wife withdraws $10: false Wife withdraws $10: false Wife withdraws $10: false Wife withdraws $10: false Wife withdraws $10: false

Wife withdraws $10: true Wife withdraws $10: false Wife withdraws $10: false Wife withdraws $10: false Wife withdraws $10: false Wife withdraws $10: false

The reason why more money is withdrawn than is available for withdrawal is that a race condition exists between the husband and wife threads.

NOTE: A race condition is a scenario in which multiple threads update the same object at the same time or nearly at the same time. Part of the object stores values written to it by one thread, and another part of the object stores values written to it by another thread.

The race condition exists because the actions of checking the amount for withdrawal to ensure that it is less than what appears in the balance and deducting the amount from the balance are not atomic (indivisible) operations. (Although atoms are divisible, atomic is commonly used to refer to something being indivisible.)

NOTE: The Thread.sleep() method call that sleeps for a variable amount of time (up to a maximum of 199 milliseconds) is present so that you can observe more money being withdrawn than is available for withdrawal. Without this method call, you might have to execute the application hundreds of times (or more) to witness this problem, because the scheduler might rarely pause a thread between the amount <= balance expression and the balance -= amount; expression statement—the code executes rapidly.

Consider the following scenario:

■ The Husband thread executes withdraw()'s amount <= balance expression, which returns true. The scheduler pauses the Husband thread and lets the Wife thread execute.

■ The Wife thread executes withdraw()'s amount <= balance expression, which returns true.

■ The Wife thread performs the withdrawal. The scheduler pauses the Wife thread and lets the Husband thread execute.

■ The Husband thread performs the withdrawal.

This problem can be corrected by synchronizing access to withdraw() so that only one thread at a time can execute inside this method. You synchronize access at the method level by adding reserved word synchronized to the method header prior to the method's return type; for example, public synchronized boolean withdraw(int amount).

As I demonstrate later, you can also synchronize access to a block of statements by specifying synchronized(object) { /* synchronized statements */ }, where object is an arbitrary object reference. No thread can enter a synchronized method or block until execution leaves the method/block; this is known as mutual exclusion.

Synchronization is implemented in terms of monitors and locks. A monitor is a concurrency construct for controlling access to a critical section, a region of code that must execute atomically. It is identified at the source code level as a synchronized method or a synchronized block.

A lock is a token that a thread must acquire before a monitor allows that thread to execute inside a monitor's critical section. The token is released automatically when the thread exits the monitor, to give another thread an opportunity to acquire the token and enter the monitor.

NOTE: A thread that has acquired a lock does not release this lock when it calls one of Thread's sleep() methods.

A thread entering a synchronized instance method acquires the lock associated with the object on which the method is called. A thread entering a synchronized class method acquires the lock associated with the class's Class object. Finally, a thread entering a synchronized block acquires the lock associated with the block's controlling object.

TIP: Thread declares a public static boolean holdsLock(Object o) method that returns true when the calling thread holds the monitor lock on object o. You will find this method handy in assertion statements, such as assert Thread.holdsLock(o);.

The need for synchronization is often subtle. For example, Listing 7-9's ID utility class declares a getNextID() method that returns a unique long-based ID, perhaps to be used when generating unique filenames. Although you might not think so, this method can cause data corruption and return duplicate values.

Listing 7-9. A utility class for returning unique IDs public class ID {

private static long nextID = 0; public static long getNextID()

return nextID++;

There are two lack-of-synchronization problems with getNextID(). Because 32-bit virtual machine implementations require two steps to update a 64-bit long integer, adding 1 to nextID is not atomic: the scheduler could interrupt a thread that has only updated half of nextID, which corrupts the contents of this variable.

NOTE: Variables of type long and double are subject to corruption when being written to in an unsynchronized context on 32-bit virtual machines. This problem does not occur with variables of type boolean, byte, char, float, int, or short; each type occupies 32 bits or less.

Assume that multiple threads call getNextID(). Because postincrement (++) reads and writes the nextID field in two steps, multiple threads might retrieve the same value. For example, thread A executes ++, reading nextID but not incrementing its value before being interrupted by the scheduler. Thread B now executes and reads the same value.

Both problems can be corrected by synchronizing access to nextID so that only one thread can execute this method's code. All that is required is to add synchronized to the method header prior to the method's return type; for example, public static synchronized int getNextID().

Synchronization is also used to communicate between threads. For example, you might design your own mechanism for stopping a thread (because you cannot use Thread's unsafe stop() methods for this task). Listing 7-10 shows how you might accomplish this task.

Listing 7-10. Attempting to stop a thread public static void main(String[] args) {

class StoppableThread extends Thread {

private boolean stopped = false; («Override public void run() {

while(!stopped)

System.out.println("running");

public void stopThread() {

stopped = true;

StoppableThread thd = new StoppableThread(); thd.start();

Thread.sleep(l000); // sleep for 1 second

catch (InterruptedException ie) {

thd.stopThread();

Listing 7-10 introduces a main() method with a local class named StoppableThread that subclasses Thread. StoppableThread declares a stopped field initialized to false, a stopThread() method that sets this field to true, and a run() method whose infinite loop checks stopped on each loop iteration to see if its value has changed to true.

After instantiating StoppableThread, the default main thread starts the thread associated with this Thread object. It then sleeps for one second and calls StoppableThread's stop() method before dying. When you run this application on a single-processor/single-core machine, you will probably observe the application stopping.

You might not see this stoppage when the application runs on a multiprocessor machine or a uniprocessor machine with multiple cores. For performance reasons, each processor or core probably has its own cache with its own copy of stopped. When one thread modifies its copy of this field, the other thread's copy of stopped is not changed.

Listing 7-11 refactors Listing 7-10 to guarantee that the application will run correctly on all kinds of machines.

Listing 7-11. Guaranteed stoppage on a multiprocessor/multicore machine public static void main(String[] args) {

class StoppableThread extends Thread {

private boolean stopped = false;

^Override public void run()

while(!isStopped())

System.out.println("running");

public synchronized void stopThread()

stopped = true;

private synchronized boolean isStopped()

return stopped;

StoppableThread thd = new StoppableThread(); thd.start();

Thread.sleep(l000); // sleep for 1 second

catch (InterruptedException ie) {

thd.stopThread();

Listing 7-11's stopThread() and isStopped() methods are synchronized to support thread communication (between the default main thread that calls stopThread() and the started thread that executes inside run()). When a thread enters one of these methods, it is guaranteed to access a single shared copy of the stopped field (not a cached copy).

Synchronization is necessary to support mutual exclusion or mutual exclusion combined with thread communication. However, there exists an alternative to synchronization when the only purpose is to communicate between threads. This alternative is reserved word volatile, which Listing 7-12 demonstrates.

Listing 7-12. The volatile alternative to synchronization for thread communication public static void main(String[] args) {

class StoppableThread extends Thread {

private volatile boolean stopped = false; ^Override public void run() {

while(!stopped)

System.out.println("running");

public void stopThread() {

stopped = true;

StoppableThread thd = new StoppableThread(); thd.start();

Thread.sleep(l000); // sleep for 1 second

catch (InterruptedException ie) {

thd.stopThread();

Listing 7-12 declares stopped to be volatile; threads that access this field will always access a single shared copy (not cached copies on multiprocessor/multicore machines). In addition to generating code that is less verbose, volatile might offer improved performance over synchronization.

CAUTION: You should only use volatile in the context of thread communication. Also, you can only use this reserved word in the context of field declarations. Although you can declare double and long fields volatile, you should avoid doing so on 32-bit virtual machines because it takes two operations to access a double or long variable's value, and mutual exclusion via synchronization is required to access their values safely.

Object's wait(), notify(), and notifyAll() methods support a form of thread communication where a thread voluntarily waits for some condition (a prerequisite for continued execution) to arise, at which time another thread notifies the waiting thread that it can continue. wait() causes its calling thread to wait on an object's monitor, and notify() and notifyAll() wake up one or all threads waiting on the monitor.

CAUTION: Because the wait(), notify(), and notifyAll() methods depend on a lock, they cannot be called from outside of a synchronized method or synchronized block. If you fail to heed this warning, you will encounter a thrown instance of the java.lang.IllegalMonitorStateException class. Also, a thread that has acquired a lock releases this lock when it calls one of Object's wait() methods.

A classic example of thread communication involving conditions is the relationship between a producer thread and a consumer thread. The producer thread produces data items to be consumed by the consumer thread. Each produced data item is stored in a shared variable.

Imagine that the threads are not communicating and are running at different speeds. The producer might produce a new data item and record it in the shared variable before the consumer retrieves the previous data item for processing. Also, the consumer might retrieve the contents of the shared variable before a new data item is produced.

To overcome those problems, the producer thread must wait until it is notified that the previously produced data item has been consumed, and the consumer thread must wait until it is notified that a new data item has been produced. Listing 7-13 shows you how to accomplish this task via wait() and notify().

Listing 7-13. The producer-consumer relationship public class PC {

public static void main(String[] args) {

Shared s = new Shared(); new Producer(s).start(); new Consumer(s).start();

class Shared {

private boolean writeable = true;

synchronized void setSharedChar(char c) {

while (!writeable)

catch (InterruptedException e) {} this.c = c; writeable = false; notify();

synchronized char getSharedChar() {

while (writeable)

catch (InterruptedException e) {} writeable = true; notify(); return c;

class Producer extends Thread {

private Shared s;

Producer(Shared s) {

^Override public void run() {

synchronized(s)

s.setSharedChar(ch);

System.out.println(ch + " produced by producer.");

class Consumer extends Thread {

private Shared s;

Consumer(Shared s) {

^Override public void run() {

char ch;

synchronized(s)

System.out.println(ch + " consumed by consumer.");

The application creates a Shared object and two threads that get a copy of the object's reference. The producer calls the object's setSharedChar() method to save each of 26 uppercase letters; the consumer calls the object's getSharedChar() method to acquire each letter.

The writeable instance field tracks two conditions: the producer waiting on the consumer to consume a data item, and the consumer waiting on the producer to produce a new data item. It helps coordinate execution of the producer and consumer. The following scenario, where the consumer executes first, illustrates this coordination:

1. The consumer executes s.getSharedChar() to retrieve a letter.

2. Inside of that synchronized method, the consumer calls wait() because writeable contains true. The consumer now waits until it receives notification from the producer.

3. The producer eventually executes s.setSharedChar(ch);.

4. When the producer enters that synchronized method (which is possible because the consumer released the lock inside of the wait() method prior to waiting), the producer discovers writeable's value to be true and does not call wait().

5. The producer saves the character, sets writeable to false (which will cause the producer to wait on the next setSharedChar() call when the consumer has not consumed the character by that time), and calls notify() to awaken the consumer (assuming the consumer is waiting).

6. The producer exits setSharedChar(char c).

7. The consumer wakes up (and reacquires the lock), sets writeable to true (which will cause the consumer to wait on the next getSharedChar() call when the producer has not produced a character by that time), notifies the producer to awaken that thread (assuming the producer is waiting), and returns the shared character.

Although the synchronization works correctly, you might observe output (on some platforms) that shows multiple producing messages before a consuming message. For example, you might see A produced by producer., followed by B produced by producer., followed by A consumed by consumer., at the beginning of the application's output.

This strange output order is caused by the call to setSharedChar() followed by its companion System.out.println() method call not being atomic, and by the call to getSharedChar() followed by its companion System.out.println() method call not being atomic. The output order is corrected by wrapping each of these method call pairs in a synchronized block that synchronizes on the Shared object referenced by s.

When you run this application, its output should always appear in the same alternating order, as shown next (only the first few lines are shown for brevity):

A produced by producer. A consumed by consumer. B produced by producer. B consumed by consumer. C produced by producer. C consumed by consumer. D produced by producer. D consumed by consumer.

CAUTION: Never call wait() outside of a loop. The loop tests the condition (!writeable or writeable in the previous example) before and after the wait() call. Testing the condition before calling wait() ensures liveness. If this test was not present, and if the condition held and notify() had been called prior to wait() being called, it is unlikely that the waiting thread would ever wake up. Retesting the condition after calling wait() ensures safety. If retesting did not occur, and if the condition did not hold after the thread had awakened from the wait() call (perhaps another thread called notify() accidentally when the condition did not hold), the thread would proceed to destroy the lock's protected invariants.

Too much synchronization can be problematic. If you are not careful, you might encounter a situation where locks are acquired by multiple threads, neither thread holds its own lock but holds the lock needed by some other thread, and neither thread can enter and later exit its critical section to release its held lock because some other thread holds the lock to that critical section. Listing 7-14's atypical example demonstrates this scenario, which is known as deadlock.

Listing 7-14. A pathological case of deadlock public class Deadlock {

private Object lockl = new Object(); private Object lock2 = new Object();

public void instanceMethod1() {

synchronized(lock1)

synchronized(lock2)

System.out.println("first thread in instanceMethodl"); // critical section guarded first by

// lockl and then by lock2

public void instanceMethod2() {

synchronized(lock2)

synchronized(lock1)

System.out.println("second thread in instanceMethod2"); // critical section guarded first by // lock2 and then by lockl

public static void main(String[] args) {

final Deadlock dl = new Deadlock();

Runnable rl = new Runnable() {

^Override public void run() {

while(true)

dl.instanceMethodl();

Thread thdA = new Thread(rl);

Runnable r2 = new Runnable() {

^Override public void run() {

while(true)

dl.instanceMethod2();

Thread thdB = new Thread(r2);

Listing 7-14's thread A and thread B call instanceMethodl() and instanceMethod2(), respectively, at different times. Consider the following execution sequence:

1. Thread A calls instanceMethodl(), obtains the lock assigned to the lockl-referenced object, and enters its outer critical section (but has not yet acquired the lock assigned to the lock2-referenced object).

2. Thread B calls instanceMethod2(), obtains the lock assigned to the lock2-referenced object, and enters its outer critical section (but has not yet acquired the lock assigned to the lockl-referenced object).

3. Thread A attempts to acquire the lock associated with lock2. The virtual machine forces the thread to wait outside of the inner critical section because thread B holds that lock.

4. Thread B attempts to acquire the lock associated with lock1. The virtual machine forces the thread to wait outside of the inner critical section because thread A holds that lock.

5. Neither thread can proceed because the other thread holds the needed lock. We have a deadlock situation and the program (at least in the context of the two threads) freezes up.

Although the previous example clearly identifies a deadlock state, it is often not that easy to detect deadlock. For example, your code might contain the following circular relationship among various classes (in several source files):

■ Class A's synchronized method calls class B's synchronized method.

■ Class B's synchronized method calls class C's synchronized method.

■ Class C's synchronized method calls class A's synchronized method.

If thread A calls class A's synchronized method and thread B calls class C's synchronized method, thread B will block when it attempts to call class A's synchronized method and thread A is still inside of that method. Thread A will continue to execute until it calls class C's synchronized method, and then block. Deadlock results.

NOTE: Neither the Java language nor the virtual machine provides a way to prevent deadlock, and so the burden falls on you. The simplest way to prevent deadlock from happening is to avoid having either a synchronized method or a synchronized block call another synchronized method/block. Although this advice prevents deadlock from happening, it is impractical because one of your synchronized methods/blocks might need to call a synchronized method in a Java API, and the advice is overkill because the synchronized method/block being called might not call any other synchronized method/block, so deadlock would not occur.

You will sometimes want to associate per-thread data (such a user ID) with a thread. Although you can accomplish this task with a local variable, you can only do so while the local variable exists. You could use an instance field to keep this data around longer, but then you would have to deal with synchronization. Thankfully, Java supplies ThreadLocal as a simple alternative.

Each instance of the ThreadLocal class describes a thread-local variable, which is a variable that provides a separate storage slot to each thread that accesses the variable. You can think of a thread-local variable as a multislot variable in which each thread can store a different value in the same variable. Each thread sees only its value and is unaware of other threads having their own values in this variable.

ThreadLocal is generically declared as ThreadLocal<T>, where T identifies the type of value that is stored in the variable. This class declares the following constructor and methods:

■ ThreadLocal() creates a new thread-local variable.

■ T get() returns the value in the calling thread's storage slot. If an entry does not exist when the thread calls this method, get() calls initialValue().

■ T initialValue() creates the calling thread's storage slot and stores an initial (default) value in this slot. The initial value defaults to null. You must subclass ThreadLocal and override this protected method to provide a more suitable initial value.

■ void remove() removes the calling thread's storage slot. If this method is followed by get() with no intervening set(), get() calls initialValue().

■ void set(T value) sets the value of the calling thread's storage slot to value.

Listing 7-15 shows you how to use ThreadLocal to associate a different user ID with each of two threads.

Listing 7-15. Different user IDs for different threads private static volatile ThreadLocal<String> userID = new ThreadLocal<String>();

public static void main(String[] args) {

^Override public void run() {

String name = Thread.currentThread().getName(); if (name.equals("A")) userID.set("foxtrot");

else userID.set("charlie");

System.out.println(name + " " + userID.get());

Thread thdA = new Thread(r);

thdA.setName("A");

Thread thdB = new Thread(r);

thdB.setName("B");

After instantiating ThreadLocal and assigning the reference to a volatile class field named userID (the field is volatile because it is accessed by different threads, which might execute on a multiprocessor/multicore machine), the default main thread creates two more threads that store different String objects in userID and output their objects.

When you run this application, you will observe the following output (possibly not in this order):

A foxtrot B charlie

Values stored in thread-local variables are not related. When a new thread is created, it gets a new storage slot containing initialValue()'s value. Perhaps you would prefer to pass a value from a parent thread, a thread that creates another thread, to a child thread, the created thread. You accomplish this task with InheritableThreadLocal.

InheritableThreadLocal is a subclass of ThreadLocal. In addition to declaring a public InheritableThreadLocal() constructor, this class declares the following protected method:

■ T childValue(T parentValue) calculates the child's initial value as a function of the parent's value at the time the child thread is created. This method is called from the parent thread before the child thread is started. The method returns the argument passed to parentValue and should be overridden when another value is desired.

Listing 7-16 shows you how to use InheritableThreadLocal to pass a parent thread's Integer object to a child thread.

Listing 7-16. Passing an object from parent thread to child thread private static volatile InheritableThreadLocal<Integer> intVal = new InheritableThreadLocal<Integer>();

public static void main(String[] args) {

Runnable rP = new Runnable() {

^Override public void run() {

intVal.set(new Integer(10));

Runnable rC = new Runnable() {

Thread thd;

thd = Thread.currentThread(); String name = thd.getName(); System.out.println(name + " " + intVal.get());

Thread thdChild = new Thread(rC);

thdChild.setName("Child");

thdChild.start();

After instantiating InheritableThreadLocal and assigning it to a volatile class field named intVal, the default main thread creates a parent thread, which stores an Integer object containing 10 in intVal. The parent thread creates a child thread, which accesses intVal and retrieves its parent thread's Integer object.

When you run this application, you will observe the following output: Child 10

EXERCISES

The following exercises are designed to test your understanding of this chapter's additional basic APIs:

1. What is reflection?

2. What is the difference between Class's getDeclaredFields() and getFields() methods?

3. How would you determine if the method represented by a Method object is abstract?

4. Identify the three ways of obtaining a Class object.

5. True or false: A string literal is a String object.

6. What is the purpose of String's intern() method?

7. How do String and StringBuffer differ?

8. How do StringBuffer and StringBuilder differ?

9. What does System's arraycopy() method accomplish?

10. What is a thread?

11. What is the purpose of the Runnable interface?

12. What is the purpose of the Thread class?

13. True or false: A Thread object associates with multiple threads.

14. What is a race condition?

15. What is synchronization?

16. How is synchronization implemented?

17. How does synchronization work?

18. True or false: Variables of type long or double are not atomic on 32-bit virtual machines.

19. What is the purpose of reserved word volatile?

20. True or false: Object's wait() methods can be called from outside of a synchronized method or block.

21. What is deadlock?

22. What is the purpose of the ThreadLocal class?

23. How does InheritableThreadLocal differ from ThreadLocal?

24. In Chapter 6, Listing 6-14's demonstration of the SoftReference class includes the following array declaration and inefficient loop:

String[] imageNames = new String[NUM_IMAGES]; for (int i = 0; i < imageNames.length; i++)

imageNames[i] = new String("image" + i + ".gif");

Rewrite this loop to use StringBuffer.

25. Class declares boolean isAnnotation(), boolean isEnum(), and boolean isInterface() methods that return true when the Class object represents an annotation type, an enum, or an interface, respectively. Create a Classify application that uses Class's forName() method to load its single command-line argument, which will represent an annotation type, enum, interface, or class (the default). Use a chained if-else statement along with the aforementioned methods to output Annotation, Enum, Interface, or Class.

26. The output from Listing 7-1's ExploreType application does not look like a class declaration for the Boolean class. Improve this application so that java ExploreType java.lang.Boolean generates the following output:

public class java.lang.Boolean {

public static final java.lang.Boolean java.lang.Boolean.TRUE public static final java.lang.Boolean java.lang.Boolean.FALSE public static final java.lang.Class java.lang.Boolean.TYPE private final boolean java.lang.Boolean.value private static final long java.lang.Boolean.serialVersionUID

// CONSTRUCTORS

public java.lang.Boolean(java.lang.String) public java.lang.Boolean(boolean)

// METHODS

public int java.lang.Boolean.hashCode()

public boolean java.lang.Boolean.equals(java.lang.Object)

public int java.lang.Boolean.compareTo(java.lang.Boolean)

public int java.lang.Boolean.compareTo(java.lang.Object)

public static boolean java.lang.Boolean.getBoolean(java.lang.String)

public static java.lang.String java.lang.Boolean.toString(boolean)

public java.lang.String java.lang.Boolean.toString()

public static java.lang.Boolean java.lang.Boolean.valueOf(java.lang.String) public static java.lang.Boolean java.lang.Boolean.valueOf(boolean) public boolean java.lang.Boolean.booleanValue()

public static boolean java.lang.Boolean.parseBoolean(java.lang.String) private static boolean java.lang.Boolean.toBoolean(java.lang.String)

27. Modify Listing 7-3's CountingThreads application by marking the two started threads as daemon threads. What happens when you run the resulting application?

28. Modify Listing 7-3's CountingThreads application by adding logic to stop both counting threads when the user presses the Enter key. The default main thread should call System.in.read() prior to terminating, and assign true to a variable named stopped after this method call returns. Each counting thread should test this variable to see if it contains true at the start of each loop iteration, and only continue the loop when the variable contains false.

Was this article helpful?

0 0

Post a comment