Chapter 8. Inter-thread Communication

IN THIS CHAPTER

When multiple threads are running inside an application, most of them will need to communicate with each other in some form. In Chapter 5, "Gracefully Stopping Threads," crude inter-thread communication was accomplished by one thread interrupting another thread. As a general guideline, the use of interrupt() should be reserved for situations where you want to interrupt a thread to signal it to die gracefully.

In the last chapter, you saw how the use of synchronized (or in some cases the use of volatile) is needed to be sure that one thread can safely write values that another thread can safely read. In this chapter, I'll show you how one thread can signal another thread that it has made a change.

In this chapter, I'll show you how to use the following:

  • The wait(), notify(), and notifyAll() methods of Object

  • The join() method of Thread

  • The classes PipedInputStream, PipedOutputStream, PipedReader, and PipedWriter

  • The classes ThreadLocal and InheritableThreadLocal

The Need for Inter-thread Signaling

Through synchronization, one thread can safely change values that another thread will read. How does the second thread know that the values have changed? What if the second thread is waiting for the values to change by rereading the values every few seconds?

One not-so-good way that a thread can wait for a value to change is by using a busy/wait:

while ( getValue() != desiredValue ) {
    Thread.sleep(500);
}

Such code is called a busy/wait because the thread is busy using up processor resources to continually check to see if the value has changed. To use fewer resources, the sleep time could be increased, but then the thread might not find out about the change for quite some time. On the other hand, if the sleep time is reduced, the thread will find out sooner, but will waste even more of the processor resources. In Java, there is a much better way to handle this kind of situation: the wait/notify mechanism.

The Wait/Notify Mechanism

The wait/notify mechanism allows one thread to wait for a notification from another thread that it may proceed. Typically, the first thread checks a variable and sees that the value is not yet what it needs. The first thread invokes wait() and goes to sleep (using virtually zero processor resources) until it is notified that something has changed. Eventually, the second thread comes along and changes the value of the variable and invokes notify() (or notifyAll()) to signal the sleeping thread that the variable has been changed.

The wait/notify mechanism does not require that a variable be checked by one thread and set by another. However, it is generally a good idea to use this mechanism in conjunction with at least one variable. Doing so helps in avoiding missed notifications and in detecting early notifications (I'll tell you about both later in this chapter).

Minimal Wait/Notify

At a bare minimum, you need an object to lock on and two threads to implement the wait/notify mechanism. For thread notification, two methods are available: notify() and notifyAll(). For simplicity, I'll use notify() here and explain the difference later in this chapter.

Imagine that there is a member variable, valueLock, that will be used for synchronization:

private Object valueLock = new Object();

The first thread comes along and executes this code fragment:

synchronized ( valueLock ) {
    try {
        valueLock.wait();
    } catch ( InterruptedException x ) {
        System.out.println("interrupted while waiting");
    }
}

The wait() method requires the calling thread to have previously acquired the object-level lock on the target object. In this case, the object that will be waited upon is valueLock, and two lines before the valueLock.wait() statement is the synchronized(valueLock) statement. The thread that invokes the wait() method releases the object-level lock and goes to sleep until notified or interrupted. If the waiting thread is interrupted, it competes with the other threads to reacquire the object-level lock and throws an InterruptedException from within wait(). If the waiting thread is notified, it competes with the other threads to reacquire the object-level lock and then returns from wait().

Many times, a thread is interrupted to signal it that it should clean up and die (see Chapter 5). The statements used to wait can be slightly rearranged to allow the InterruptedException to propagate up further:

try {
    synchronized ( valueLock ) {
        valueLock.wait();
    }
} catch ( InterruptedException x ) {
    System.out.println("interrupted while waiting");
    // clean up, and allow thread to return from run()
}

Instead of catching InterruptedException, methods can simply declare that they throw it to pass the exception further up the call chain:

public void someMethod() throws InterruptedException {
    // ...
    synchronized ( valueLock ) {
        valueLock.wait();
    }
    // ...
}

The thread doing the notification comes along and executes this code fragment:

synchronized ( valueLock ) {
    valueLock.notify();  // notifyAll() might be safer...
}

This thread blocks until it can get exclusive access to the object-level lock for valueLock. After the lock is acquired, this thread notifies one of the waiting threads. If no threads are waiting, the notification effectively does nothing. If more than one thread is waiting on valueLock, the thread scheduler arbitrarily chooses one to receive the notification. The other waiting threads are not notified and continue to wait. To notify all waiting threads (instead of just one of them), use notifyAll() (discussed later in this chapter).

Typical Wait/Notify

In most cases, a member variable is checked by the thread doing the waiting and modified by the thread doing the notification. The checking and modification occur inside the synchronized blocks to be sure that no race conditions develop.

This time, two member variables are used:

private boolean value = false;
private Object valueLock = new Object();

The value variable is checked by the thread doing the waiting and is set by the thread doing the notification. Synchronization on valueLock controls concurrent access to value.

The first thread comes along and executes this code fragment:

try {
    synchronized ( valueLock ) {
        while ( value != true ) {
           valueLock.wait();
        }

        // value is now true
    }
} catch ( InterruptedException x ) {
    System.out.println("interrupted while waiting");
}

After acquiring the object-level lock for valueLock, the first thread checks value to see if it is true. If it is not, the thread executes wait(), releasing the object-level lock. When this thread is notified, it wakes up, reacquires the lock, and returns from wait(). To be sure that it was not falsely notified (see the Early Notification discussion later in this chapter), it re-evaluates the while expression. If value is still not true, the thread waits again. If it is true, the thread continues to execute the rest of the code inside the synchronized block.

While the first thread is waiting, a second thread comes along and executes this code fragment:

synchronized (valueLock ) {
    value = true;
    valueLock.notify();  // notifyAll() might be safer...
}

When the first thread executes the wait() method on valueLock, it releases the object-level lock it was holding. This release allows the second thread to get exclusive access to the object-level lock on valueLock and enter the synchronized block. Inside, the second thread sets value to true and invokes notify() on valueLock to signal one waiting thread that value has been changed.

Wait/Notify with synchronized Methods

In the previous examples, valueLock was used with the synchronized statement to require that threads get exclusive access to the object-level lock associated with valueLock. Sometimes, the class is designed to synchronize on this instead of another object. In this case, the synchronized method modifier can be used instead of a synchronized statement. The following code fragments are an adaptation of the previous example.

As before, a member variable value is initially set to false:

private boolean value = false;

The first thread (threadA) comes along and invokes this waitUntilTrue() method:

public synchronized void waitUntilTrue()
                throws InterruptedException {

    while ( value == false ) {
       wait();
    }
}

While threadA is blocked on the wait(), a second thread (threadB) comes along and executes this method, passing in true for newValue:

public synchronized void setValue(boolean newValue) {
    if ( newValue != value ) {
        value = newValue;
        notify();  // notifyAll() might be safer...
    }
}

Note that both methods are synchronized and are members of the same class. In addition, both threads are invoking methods on the same instance of this class. The waitUntilTrue() method (with the wait() inside) declares that it might throw an InterruptedException. In this case, when threadB passes in true, value is changed and notify() is used to signal the waiting threadA that it may proceed. threadA wakes up, reacquires the object-level lock on this, returns from wait(), and re-evaluates the while expression. This time, value is true, and threadA will return from waitUntilTrue().

Figure 8.1 shows a relative timeline of when everything occurs. When threadA wants to invoke waitUntilTrue(), it must first get exclusive access to the object-level lock on this (time T1). Just after threadA acquires the lock, it enters waitUntilTrue() (time T2). threadA determines that it must wait for value to change, so it invokes the wait() method on this (time T3). Just after threadA enters wait(), it releases the object-level lock on this (time T4).

Some time later, threadB acquires the lock (time T5) and enters the setValue() method (time T6). While inside setValue(), threadB sets value to true and invokes notify(). This action causes threadA to be notified, but threadA cannot return from wait() until it can get the lock, and threadB is still holding the lock. threadB then returns from setValue() (time T7) and releases the lock (time T8).

Soon after threadB releases the object-level lock on this, threadA is able to reacquire it (time T9) and returns from wait() (time T10). threadA sees that value is now true and proceeds to execute the rest of the waitUntilTrue() method. threadA returns from waitUntilTrue() (time T11) and releases the lock (time T12).

There are some particular points of interest in Figure 8.1. First, notice the small intervals of time between some events. For example, there is a very short, but non-zero interval between the time that threadA gets the lock (time T1) and the time that it is inside waitUntilTrue() (time T2). Also notice that threadA is inside wait() from time T3 through time T10, but releases the lock shortly after entering (time T4) and reacquires it just before returning (time T9). A thread might spend quite a bit of time inside a wait() method, but it is not holding the lock for most of that time. On the other hand, the time spent inside the notify() method is very brief, and the lock is held the whole time.

Timeline of events for wait/notify example.

Figure 8.1. Timeline of events for wait/notify example.

Object API Used for Wait/Notify

The wait/notify mechanism is embedded deep in the heart of Java. Object, the superclass of all classes, has five methods that are the core of the wait/notify mechanism: notify(), notifyAll(), wait(), wait(long), and wait(long, int). All classes in Java inherit from Object, so all classes have these public methods available to them. Additionally, none of these methods can be overridden in a subclass as they are all declared final.

notify()

public final native void notify()
        throws IllegalMonitorStateException  // RuntimeException

The notify() method is used by a thread to signal any other threads that might be waiting on the object. If more than one thread is waiting on the object, the thread scheduler will arbitrarily choose exactly one to be notified, and the others will continue to wait. If no threads are currently waiting on the object, notify() has no effect. Before invoking notify(), a thread must get exclusive access to the object-level lock for the object. Unlike wait(), the invocation of notify() does not temporarily release the lock. If the proper lock is not held when notify() is called, an IllegalMonitorStateException is thrown. This exception is a subclass of RuntimeException, so a try-catch construct is not necessary and is rarely used.

notifyAll()

public final native void notifyAll()
        throws IllegalMonitorStateException  // RuntimeException

The notifyAll() method works the same as notify() (see above) with one important exception: When notifyAll() is invoked, all the threads waiting on the object are notified, not just one. The advantage of notifyAll() is that you don't have to be concerned about which one of the waiting threads will be notified—they will all be notified. The disadvantage is that it might be wasteful (in terms of processor resources) to notify all the waiting threads if only one will actually be able to proceed. When in doubt, err on the side of safety over speed and use notifyAll() instead of notify().

wait()

public final void wait()
        throws InterruptedException,
               IllegalMonitorStateException  // RuntimeException

The wait() method is used to put the current thread to sleep until it is notified or interrupted. Before invoking wait(), a thread must get exclusive access to the object-level lock for the object. Just after entering wait(), the current thread releases the lock. Before returning from wait(), the thread competes with the other threads to reacquire the lock. If the proper lock is not held when wait() is called, an IllegalMonitorStateException is thrown. This exception is a subclass of RuntimeException, so a try-catch construct is not necessary and is rarely used.

If the waiting thread is interrupted, it competes to reacquire the lock and throws an InterruptedException from within wait(). This exception is not a subclass of RuntimeException, so a try-catch construct is required.

wait(long)

public final native void wait(long msTimeout)
        throws InterruptedException,
               IllegalMonitorStateException, // RuntimeException
               IllegalArgumentException      // RuntimeException

The wait(long) method is used to put the current thread to sleep until it is notified, interrupted, or the specified timeout elapses. Other than the timeout, wait(long) behaves the same as wait() (see above). The argument msTimeout specifies the maximum number of milliseconds that the thread should wait for notification. If msTimeout is 0, the thread will never time out (just like wait()). If the argument is less than 0, an IllegalArgumentException will be thrown. IllegalArgumentException is a subclass of RuntimeException, so a try-catch block is not required and is rarely used.

If the specified number of milliseconds elapses before the waiting thread is notified or interrupted, it competes to reacquire the lock and returns from wait(long). There is no way for the caller to determine whether a notification or a timeout occurred because no information (void) is returned from wait(long).

wait(long, int)

public final void wait(long msTimeout, int nanoSec)
        throws InterruptedException,
               IllegalMonitorStateException, // RuntimeException
               IllegalArgumentException      // RuntimeException

The wait(long, int) method works just like wait(long, int) (see above) with the exception that nanoseconds can be added to the timeout value. The argument nanoSec is added to msTimeout to determine the total amount of time that the thread will wait for notification before returning. A nanosecond is one-billionth of a second (10E-9), and most common implementations of the Java VM don't truly support this fine a resolution of time. For this reason, the use of the method is currently quite rare.

When to Use notifyAll() Instead of notify()

The fundamental difference between notify() and notifyAll() is that if more than one thread is simultaneously waiting for notification, notify() will provide notification to only one of the waiting threads, whereas notifyAll() will provide notification to all of them. If your code is well defended against early notifications (discussed later), notifyAll() is generally the better choice.

The major disadvantage of notifyAll() is that it is wasteful of processor resources if all but one of the notified threads will end up waiting again. This is a situation that is difficult to guarantee. If your code synchronizes on this either through synchronized blocks or the synchronized method modifier, you can't be sure that some code external to the class won't synchronize and wait on a reference to the object. If that happens, notify() might signal the thread running that external code instead of the thread that you intended. Consider a situation where you have a class, ClassX, with two methods:

public synchronized void waitUntilTrue()
                throws InterruptedException {

    while ( value == false ) {
       wait();
    }
}

public synchronized void setValue(boolean newValue) {
    if ( newValue != value ) {
        value = newValue;
        notify();  // notifyAll() might be safer...
    }
}

In addition, there's an external class, ClassY, with this code in one of its methods:

ClassX cx = new ClassX();
cx.setValue(false);
// ...
synchronized ( cx ) {
    cx.wait(); // trouble
}

If threadA is running inside ClassY, it synchronizes on cx and invokes wait(). If threadB invokes waitUntilTrue(), it is now also waiting for notification. If threadC invokes setValue() and passes true (a new value), threadC will only notify one thread because notifyAll() wasn't used. There's no way to be sure whether threadA or threadB will be notified. In this situation, notifyAll() would have guaranteed that they would both be notified.

It is generally safe to use notify() only when you can guarantee that only one thread will ever be waiting for notification. This is a relatively unusual occurrence.

Tip

If you're not sure whether you need to use notify() or notifyAll(), use notifyAll(). It might be a little wasteful, but it's safer.

Missed Notification

A missed notification occurs when threadB tries to notify threadA, but threadA is not yet waiting for the notification. In a multithreaded environment like Java, you don't have much control over which thread runs and for how long. This uncertainty can lead to a situation in which most of the time an application is run, threadA is waiting before threadB does the notification. But occasionally, threadB does the notification before threadA is waiting. This missed notification scenario can be quite dangerous.

MissedNotify

MissedNotify (see Listing 8.1) demonstrates how a notification can be missed.

Example 8.1. MissedNotify.java—An Example of How a Notification Can Be Missed

 1: public class MissedNotify extends Object {
 2:     private Object proceedLock;
 3:
 4:     public MissedNotify() {
 5:         print("in MissedNotify()");
 6:         proceedLock = new Object();
 7:     }
 8:
 9:     public void waitToProceed() throws InterruptedException {
10:         print("in waitToProceed() - entered");
11:
12:         synchronized ( proceedLock ) {
13:             print("in waitToProceed() - about to wait()");
14:             proceedLock.wait();
15:             print("in waitToProceed() - back from wait()");
16:         }
17:
18:         print("in waitToProceed() - leaving");
19:     }
20:
21:     public void proceed() {
22:         print("in proceed() - entered");
23:
24:         synchronized ( proceedLock ) {
25:             print("in proceed() - about to notifyAll()");
26:             proceedLock.notifyAll();
27:             print("in proceed() - back from notifyAll()");
28:         }
29:
30:         print("in proceed() - leaving");
31:     }
32:
33:     private static void print(String msg) {
34:         String name = Thread.currentThread().getName();
35:         System.out.println(name + ": " + msg);
36:     }
37:
38:     public static void main(String[] args) {
39:         final MissedNotify mn = new MissedNotify();
40: 
41:         Runnable runA = new Runnable() {
42:                 public void run() {
43:                     try {
44:                         Thread.sleep(1000);
45:                         mn.waitToProceed();
46:                     } catch ( InterruptedException x ) {
47:                         x.printStackTrace();
48:                     }
49:                 }
50:             };
51:
52:         Thread threadA = new Thread(runA, "threadA");
53:         threadA.start();
54:
55:         Runnable runB = new Runnable() {
56:                 public void run() {
57:                     try {
58:                         Thread.sleep(500);
59:                         mn.proceed();
60:                     } catch ( InterruptedException x ) {
61:                         x.printStackTrace();
62:                     }
63:                 }
64:             };
65:
66:         Thread threadB = new Thread(runB, "threadB");
67:         threadB.start();
68:
69:         try {
70:             Thread.sleep(10000);
71:         } catch ( InterruptedException x ) {
72:         }
73:
74:         print("about to invoke interrupt() on threadA");
75:         threadA.interrupt();
76:     }
77: }

The thread entering the waitToProceed() method (lines 9–19) blocks until it can get exclusive access to the object-level lock on proceedLock (line 12). After it does, it invokes wait() to go to sleep and await notification (line 14). The InterruptedException that might be thrown by wait() is passed along and is declared to be thrown from waitToProceed() (line 9).

The thread entering the proceed() method (lines 21–31) blocks until it gets the lock on proceedLock (line 24). It then invokes notifyAll() to signal any and all waiting threads (line 26).

In the main() method (lines 38–76), an instance of MissedNotify called mn is constructed (line 39) and two threads are spawned to interact with it. The first thread is threadA, which sleeps for one second (line 44) before invoking waitToProceed() (line 45). The second thread is threadB, which sleeps for 0.5 seconds (line 58) before invoking proceed() (line 59). Because threadB sleeps only half the time that threadA does, threadB will enter and exit proceed() while threadA is still sleeping. When threadA enters waitToProceed(), it will end up waiting indefinitely because it will have missed the notification.

After 10 seconds have elapsed (lines 69–72), the main thread invokes interrupt() on threadA (line 75) to get it to break out of wait(). Inside wait(), threadA throws an InterruptedException that causes waitToProceed() to abruptly terminate. The exception is caught (line 46) and a stack trace is printed (line 47).

Listing 8.2 shows the output produced when MissedNotify is run. Your output should match.

Example 8.2. Output from MissedNotify

 1: main: in MissedNotify()
 2: threadB: in proceed() - entered
 3: threadB: in proceed() - about to notifyAll()
 4: threadB: in proceed() - back from notifyAll()
 5: threadB: in proceed() - leaving
 6: threadA: in waitToProceed() - entered
 7: threadA: in waitToProceed() - about to wait()
 8: main: about to invoke interrupt() on threadA
 9: java.lang.InterruptedException: operation interrupted
10:     at java.lang.Object.wait(Native Method)
11:     at java.lang.Object.wait(Object.java:424)
12:     at MissedNotify.waitToProceed(MissedNotify.java:14)
13:     at MissedNotify$1.run(MissedNotify.java:45)
14:     at java.lang.Thread.run(Thread.java:479)

You can see that threadB gets into proceed(), performs the notification, and leaves proceed() (lines 2–5). Long after threadB is finished, threadA enters waitToProceed() (line 6) and continues on to invoke wait() (line 7). threadA remains stuck here until the main thread interrupts it (lines 8–14).

Figure 8.2 shows the approximate sequence of events that occurs when MissedNotify is run. The lock on proceedLock is held by threadB from shortly after it enters proceed() until just before it returns from the method (from time T2 to T3). Soon after threadB leaves proceed() (time T4), threadA enters waitToProceed() (time T5). threadA acquires the lock on proceedLock (time T6) and invokes wait() (time T7). Just after entering the wait() method, it releases the lock (time T8). threadA remains inside wait() indefinitely because it has missed the notification.

Timeline of events for a missed notification.

Figure 8.2. Timeline of events for a missed notification.

MissedNotifyFix

To fix MissedNotify, a boolean indicator variable should be added. The indicator is only accessed and modified inside synchronized blocks. This indicator will be initially false and will be set true whenever the proceed() method happens to be called. Inside waitToProceed(), the indicator will be checked to see if a wait is necessary or not. Listing 8.3 shows the code for MissedNotifyFix.

Example 8.3. MissedNotifyFix.java—A Fixed Version of MissedNotify

 1: public class MissedNotifyFix extends Object {
 2:     private Object proceedLock;
 3:     private boolean okToProceed;
 4:
 5:     public MissedNotifyFix() {
 6:         print("in MissedNotify()");
 7:         proceedLock = new Object();
 8:         okToProceed = false;
 9:     }
10:
11:     public void waitToProceed() throws InterruptedException {
12:         print("in waitToProceed() - entered");
13:
14:         synchronized ( proceedLock ) {
15:             print("in waitToProceed() - entered sync block");
16:
17:             while ( okToProceed == false ) {
18:                 print("in waitToProceed() - about to wait()");
19:                 proceedLock.wait();
20:                 print("in waitToProceed() - back from wait()");
21:             }
22:
23:             print("in waitToProceed() - leaving sync block");
24:         }
25:
26:         print("in waitToProceed() - leaving");
27:     }
28:
29:     public void proceed() {
30:         print("in proceed() - entered");
31:
32:         synchronized ( proceedLock ) {
33:             print("in proceed() - entered sync block");
34:
35:             okToProceed = true;
36:             print("in proceed() - changed okToProceed to true");
37:             proceedLock.notifyAll();
38:             print("in proceed() - just did notifyAll()");
39:
40:             print("in proceed() - leaving sync block");
41:         }
42: 
43:         print("in proceed() - leaving");
44:     }
45:
46:     private static void print(String msg) {
47:         String name = Thread.currentThread().getName();
48:         System.out.println(name + ": " + msg);
49:     }
50:
51:     public static void main(String[] args) {
52:         final MissedNotifyFix mnf = new MissedNotifyFix();
53:
54:         Runnable runA = new Runnable() {
55:                 public void run() {
56:                     try {
57:                         Thread.sleep(1000);
58:                         mnf.waitToProceed();
59:                     } catch ( InterruptedException x ) {
60:                         x.printStackTrace();
61:                     }
62:                 }
63:             };
64:
65:         Thread threadA = new Thread(runA, "threadA");
66:         threadA.start();
67:
68:         Runnable runB = new Runnable() {
69:                 public void run() {
70:                     try {
71:                         Thread.sleep(500);
72:                         mnf.proceed();
73:                     } catch ( InterruptedException x ) {
74:                         x.printStackTrace();
75:                     }
76:                 }
77:             };
78:
79:         Thread threadB = new Thread(runB, "threadB");
80:         threadB.start();
81:
82:         try { 
83:             Thread.sleep(10000);
84:         } catch ( InterruptedException x ) {
85:         }
86: 
87:         print("about to invoke interrupt() on threadA");
88:         threadA.interrupt();
89:     }
90: }

MissedNotifyFix adds the private member variable okToProceed (line 3) to be used as the indicator. It is initially set to false in the constructor (line 8). The proceedLock object is used to control concurrent access to okToProceed and to facilitate the wait/notify implementation.

Inside the synchronized block (lines 32–41) of proceed() (lines 29–44), okToProceed is set true (line 35) just before notifyAll() is invoked. This way, if the notifyAll() method is ineffective because no threads are currently waiting, okToProceed indicates that proceed() has been called.

Inside the synchronized block (lines 14–24) of waitToProceed() (lines 11–27), okToProceed is checked to see if any waiting is necessary. Instead of just using an if statement to check, a while statement (lines 17–21) is used as a safeguard against early notifications (explained later in this chapter). As long as okToProceed is false, the calling thread (threadA) will wait() for notification (line 19). Even if threadA is notified while waiting, it will double-check okToProceed. If okToProceed is still false, threadA will wait() again.

In this particular case, okToProceed is set true by threadB long before threadA enters waitToProceed(). When threadA evaluates the while expression (line 17), it determines that no waiting is necessary and skips the body of the loop. The notification is still missed, but the indicator variable okToProceed prevents threadA from waiting.

Listing 8.4 shows the output produced when MissedNotifyFix is run. Your output should match. Notice that threadB gets in and out of proceed() (lines 2–7) before threadA enters waitToProceed() (line 8). With this indicator fix in place, threadA moves right through waitToProceed() (lines 8–11). Notice that none of the messages about wait() are printed. The indicator variable kept threadA from waiting for notification that would never come.

Example 8.4. Output from MissedNotifyFix

 1: main: in MissedNotify()
 2: threadB: in proceed() - entered
 3: threadB: in proceed() - entered sync block
 4: threadB: in proceed() - changed okToProceed to true
 5: threadB: in proceed() - just did notifyAll()
 6: threadB: in proceed() - leaving sync block
 7: threadB: in proceed() - leaving
 8: threadA: in waitToProceed() - entered
 9: threadA: in waitToProceed() - entered sync block
10: threadA: in waitToProceed() - leaving sync block
11: threadA: in waitToProceed() - leaving
12: main: about to invoke interrupt() on threadA

Figure 8.3 shows the sequence of events for MissedNotifyFix. The main difference to note is that threadA never calls wait().

Timeline of events for MissedNotifyFix.

Figure 8.3. Timeline of events for MissedNotifyFix.

Early Notification

If a thread is notified while waiting, but the condition the thread is waiting for has not yet been met, the thread has received an early notification. An early notification can also occur if the condition is briefly met but quickly changes so it's no longer met. This might sound strange, but early notification can happen due to subtle errors in the code (generally when an if is used instead of a while).

EarlyNotify

EarlyNotify (see Listing 8.5) shows how an early notification can occur and the resulting problems it causes. Basically, two threads are waiting to remove an item, while another thread adds just one item.

Example 8.5. EarlyNotify.java—An Example of How a Notification Can Come Too Early

 1: import java.util.*;
 2:
 3: public class EarlyNotify extends Object {
 4:     private List list;
 5:
 6:     public EarlyNotify() {
 7:         list = Collections.synchronizedList(new LinkedList());
 8:     }
 9:
10:     public String removeItem() throws InterruptedException {
11:         print("in removeItem() - entering");
12:
13:         synchronized ( list ) {
14:             if ( list.isEmpty() ) {  // dangerous to use 'if'!
15:                 print("in removeItem() - about to wait()");
16:                 list.wait();
17:                 print("in removeItem() - done with wait()");
18:             }
19:
20:             // extract the new first item
21:             String item = (String) list.remove(0);
22:
23:             print("in removeItem() - leaving");
24:             return item;
25:         } // sync
26:     }
27:
28:     public void addItem(String item) {
29:         print("in addItem() - entering");
30:         synchronized ( list ) {
31:             // There'll always be room to add to this List
32:             // because it expands as needed.
33:             list.add(item);
34:             print("in addItem() - just added: '" + item + "'");
35:
36:             // After adding, notify any and all waiting
37:             // threads that the list has changed.
38:             list.notifyAll();
39:             print("in addItem() - just notified");
40:         } // sync
41:         print("in addItem() - leaving");
42:     }
43: 
44:     private static void print(String msg) {
45:         String name = Thread.currentThread().getName();
46:         System.out.println(name + ": " + msg);
47:     }
48:
49:     public static void main(String[] args) {
50:         final EarlyNotify en = new EarlyNotify();
51:
52:         Runnable runA = new Runnable() {
53:                 public void run() {
54:                     try {
55:                         String item = en.removeItem();
56:                         print("in run() - returned: '" +
57:                                 item + "'");
58:                     } catch ( InterruptedException ix ) {
59:                         print("interrupted!");
60:                     } catch ( Exception x ) {
61:                         print("threw an Exception!!!
" + x);
62:                     }
63:                 }
64:             };
65:
66:         Runnable runB = new Runnable() {
67:                 public void run() {
68:                     en.addItem("Hello!");
69:                 }
70:             };
71:
72:         try {
73:             Thread threadA1 = new Thread(runA, "threadA1");
74:             threadA1.start();
75:
76:             Thread.sleep(500);
77:
78:             // start a *second* thread trying to remove
79:             Thread threadA2 = new Thread(runA, "threadA2");
80:             threadA2.start();
81:
82:             Thread.sleep(500);
83: 
84:             Thread threadB = new Thread(runB, "threadB");
85:             threadB.start();
86:
87:             Thread.sleep(10000); // wait 10 seconds
88:
89:             threadA1.interrupt();
90:             threadA2.interrupt();
91:         } catch ( InterruptedException x ) {
92:             // ignore
93:         }
94:     }
95: }

In the constructor for EarlyNotify (lines 6–9), a multithread-safe List is created and used to hold the items added and removed. When a thread enters the removeItem() method (lines 10–26), it blocks until it can get exclusive access to the object-level lock for list (line 13). The thread checks to see if the list is empty (line 14). If the list is empty, the thread invokes wait() and sleeps until notified (line 16). If the thread is interrupted while waiting, it throws an InterruptedException (line 10) that is passed out of removeItem(). When notified, the thread removes the first item from the list and casts it into a String (line 21). This String is returned to the caller (line 24) and in the process of leaving the synchronized block, the lock is automatically released.

When a thread enters the addItem() method (lines 28–42), it blocks until it can get exclusive access to the object-level lock for list (line 30). The thread adds the item to list (line 33) and notifies any and all waiting threads with notifyAll() that list has been modified (line 38).

In the main() method (lines 49–94), three threads are started and simultaneously interact with one instance of EarlyNotify referred to by en (line 50). threadA1 is started (line 74) and invokes removeItem() (line 55). The list is initially empty, so threadA1 invokes wait() inside removeItem(). threadA2 is started (line 80) 0.5 seconds after threadA1. threadA2 invokes removeItem() and also finds the list is empty and blocks waiting for notification.

After another 0.5 seconds pass, threadB is started (line 85) and invokes addItem(), passing in Hello! (line 68). Both threadA1 and threadA2 have temporarily released the object-level lock on list while inside wait(), so threadB is free to acquire the lock. threadB adds the String to list and notifies any and all waiting threads with notifyAll().

The trouble arises from the fact that both threadA1 and threadA2 return from wait() and try to remove the added item from the list. Only one of the two will succeed. The other will end up trying to remove an item that has just disappeared from the list.

Listing 8.6 shows possible output when EarlyNotify is run. Your output might differ somewhat. In particular, whether threadA1 or threadA2 succeeds appears to be randomly determined and can change each time EarlyNotify is run.

Example 8.6. Possible Output from EarlyNotify

 1: threadA1: in removeItem() - entering
 2: threadA1: in removeItem() - about to wait()
 3: threadA2: in removeItem() - entering
 4: threadA2: in removeItem() - about to wait()
 5: threadB: in addItem() - entering
 6: threadB: in addItem() - just added: 'Hello!'
 7: threadB: in addItem() - just notified
 8: threadB: in addItem() - leaving
 9: threadA1: in removeItem() - done with wait()
10: threadA1: in removeItem() - leaving
11: threadA1: in run() - returned: 'Hello!'
12: threadA2: in removeItem() - done with wait()
13: threadA2: threw an Exception!!!
14: java.lang.IndexOutOfBoundsException: Index: 0, Size: 0

For this particular run of EarlyNotify, threadA1 returns from wait() first (line 9) and successfully removes the item from the list (lines 10–11). When threadA2 returns from wait() (line 12), it also tries to remove the item from the list. Because the list was just emptied by threadA1, threadA2 ends up causing an exception to be thrown when it tries to remove the nonexistent item (lines 13–14). In this case, threadA2 was notified too early and should not proceed to execute the rest of removeItem().

EarlyNotifyFix

This problem of early notification is fixed in EarlyNotifyFix (see Listing 8.7). Now, when a thread returns from wait(), it rechecks the condition it was waiting for to be sure that it has been met.

Example 8.7. EarlyNotifyFix.java—Managing Early Notifications to Avoid Errors

 1: import java.util.*;
 2:
 3: public class EarlyNotifyFix extends Object {
 4:     private List list;
 5:
 6:     public EarlyNotifyFix() {
 7:         list = Collections.synchronizedList(new LinkedList());
 8:     }
 9:
10:     public String removeItem() throws InterruptedException {
11:         print("in removeItem() - entering");
12:
13:         synchronized ( list ) {
14:             while ( list.isEmpty() ) {
15:                 print("in removeItem() - about to wait()");
16:                 list.wait();
17:                 print("in removeItem() - done with wait()");
18:             }
19:
20:             // extract the new first item
21:             String item = (String) list.remove(0);
22:
23:             print("in removeItem() - leaving");
24:             return item;
25:         }
26:     }
27:
28:     public void addItem(String item) {
29:         print("in addItem() - entering");
30:         synchronized ( list ) {
31:             // There'll always be room to add to this List
32:             // because it expands as needed.
33:             list.add(item);
34:             print("in addItem() - just added: '" + item + "'");
35:
36:             // After adding, notify any and all waiting
37:             // threads that the list has changed.
38:             list.notifyAll();
39:             print("in addItem() - just notified");
40:         }
41:         print("in addItem() - leaving");
42:     }
43: 
44:     private static void print(String msg) {
45:         String name = Thread.currentThread().getName();
46:         System.out.println(name + ": " + msg);
47:     }
48:
49:     public static void main(String[] args) {
50:         final EarlyNotifyFix enf = new EarlyNotifyFix();
51:
52:         Runnable runA = new Runnable() {
53:                 public void run() {
54:                     try {
55:                         String item = enf.removeItem();
56:                         print("in run() - returned: '" +
57:                                 item + "'");
58:                     } catch ( InterruptedException ix ) {
59:                         print("interrupted!");
60:                     } catch ( Exception x ) {
61:                         print("threw an Exception!!!
" + x);
62:                     }
63:                 }
64:             };
65:
66:         Runnable runB = new Runnable() {
67:                 public void run() {
68:                     enf.addItem("Hello!");
69:                 }
70:             };
71:
72:         try {
73:             Thread threadA1 = new Thread(runA, "threadA1");
74:             threadA1.start();
75:
76:             Thread.sleep(500);
77:    
78:             // start a *second* thread trying to remove
79:             Thread threadA2 = new Thread(runA, "threadA2");
80:             threadA2.start();
81: 
82:             Thread.sleep(500);
83:    
84:             Thread threadB = new Thread(runB, "threadB");
85:             threadB.start();
86:
87:             Thread.sleep(10000); // wait 10 seconds
88:
89:             threadA1.interrupt();
90:             threadA2.interrupt();
91:         } catch ( InterruptedException x ) {
92:             // ignore
93:         }
94:     }
95: }

To properly protect the removeItem() method from early notifications, all that was necessary was to change the if to a while (line 14). Now, whenever a thread returns from wait() (line 16), it rechecks to see if it was notified early or if the list is really no longer empty (line 14). It is important that the code that checks to see if the list is empty, and the code that removes an item if it was not empty, all be within the same synchronized block.

Tip

As a general guideline for protection against early notifications, you should put your wait() statements inside while loops. This way, regardless of why the wait() statement returned, you can be sure that you proceed only when the proper conditions have been met.

Listing 8.8 shows the output produced from a particular run of EarlyNotifyFix. Your output might differ due to thread-scheduling randomness.

Example 8.8. Possible Output from EarlyNotifyFix

 1: threadA1: in removeItem() - entering
 2: threadA1: in removeItem() - about to wait()
 3: threadA2: in removeItem() - entering
 4: threadA2: in removeItem() - about to wait()
 5: threadB: in addItem() - entering
 6: threadB: in addItem() - just added: 'Hello!'
 7: threadB: in addItem() - just notified
 8: threadB: in addItem() - leaving
 9: threadA1: in removeItem() - done with wait()
10: threadA1: in removeItem() - leaving
11: threadA1: in run() - returned: 'Hello!'
12: threadA2: in removeItem() - done with wait()
13: threadA2: in removeItem() - about to wait()
14: threadA2: interrupted!

Notice that threadA1 got to the list first and removed the item (lines 9–11). After threadA2 returns from wait() (line 12), it rechecks the list and finds that the list is empty, so threadA2 ignores the early notification and invokes wait() again (line 13). After the application has been running for about 10 seconds, the main thread interrupts threadA1 and threadA2. In this case, threadA1 has already died, but threadA2 is blocked waiting inside removeItem(). When threadA2 is interrupted, wait() throws an InterruptedException (line 14).

Note

If wait(long) is notified early, it is difficult to tell whether the method returned because it was notified too early or because it timed out. In Chapter 14, "Waiting for the Full Timeout," I'll show you a technique for waiting for the full timeout to elapse.

Missed notifications and early notifications can be tough bugs to track down in your code. Many times they are exposed only under rare conditions. It is important to be careful in your coding by using an indicator variable in addition to the wait/notify mechanism.

Note

I have incorporated the combination of a boolean variable and an associated object to use for locking into a class called BooleanLock (see Chapter 17, "The BooleanLock Utility"). BooleanLock encapsulates the details of the wait/notify mechanism and prevents both missed notifications and early notifications. It can be a very useful tool in simplifying the signaling between two or more threads.

CubbyHole Example

The class CubbyHole (see Listing 8.9) simulates a cubbyhole. A cubbyhole is a slot that can have only one item in it at a time. One thread puts an item into the slot and another thread takes it out. If a thread tries to put an item into a cubbyhole that is already occupied, the thread blocks until the slot is available. If a thread tries to remove an item from an empty cubbyhole, the thread blocks until an item is added. In this example, the slot is a reference to an object. This technique allows objects to be handed off from one thread to another in a thread-safe manner.

Example 8.9. CubbyHole.java—Object Passing from One Thread to Another

 1: public class CubbyHole extends Object {
 2:     private Object slot;
 3:
 4:     public CubbyHole() {
 5:         slot = null; // null indicates empty
 6:     }
 7:
 8:     public synchronized void putIn(Object obj)
 9:                         throws InterruptedException {
10:
11:         print("in putIn() - entering");
12:
13:         while ( slot != null ) {
14:             print("in putIn() - occupied, about to wait()");
15:             wait(); // wait while slot is occupied
16:             print("in putIn() - notified, back from wait()");
17:         }
18:
19:         slot = obj;  // put object into slot
20:         print("in putIn() - filled slot, about to notifyAll()");
21:         notifyAll(); // signal that slot has been filled
22:
23:         print("in putIn() - leaving");
24:     }
25:
26:     public synchronized Object takeOut()
27:                         throws InterruptedException {
28:
29:         print("in takeOut() - entering");
30:
31:         while ( slot == null ) {
32:             print("in takeOut() - empty, about to wait()");
33:             wait(); // wait while slot is empty
34:             print("in takeOut() - notified, back from wait()");
35:         }
36:
37:         Object obj = slot;
38:         slot = null; // mark slot as empty
39:         print(
40:             "in takeOut() - emptied slot, about to notifyAll()");
41:         notifyAll(); // signal that slot is empty
42: 
43:         print("in takeOut() - leaving");
44:         return obj;
45:     }
46:
47:     private static void print(String msg) {
48:         String name = Thread.currentThread().getName();
49:         System.out.println(name + ": " + msg);
50:     }
51: }

CubbyHole has a private member variable slot (line 2) that is used to hold a reference to the object that is being passed between the threads. In the constructor, slot is set to null to indicate that it is empty (line 5).

The putIn() method (lines 8–24) is synchronized (line 8) and declares that it might throw an InterruptedException (line 9). A while loop (lines 13–17) is used to ensure that the thread that calls putIn() will not proceed until slot is empty. If slot is occupied, the calling thread invokes wait() on this (line 15) and releases the object-level lock it acquired just before entering putIn(). When slot is finally null, the thread proceeds to copy the passed parameter obj into slot (line 19) and invokes notifyAll() (line 21) to signal any and all waiting threads that data has become available.

The takeOut() method (lines 26–45) is synchronized (line 26) and declares that it might throw an InterruptedException (line 27). A while loop (lines 31–35) is used to ensure that the thread will not proceed until slot is occupied. If slot is currently empty, the thread sleeps, waiting for notification that something has changed (line 33). When slot is finally filled, the thread proceeds to copy the reference into obj (line 37), sets slot to null to indicate that it is again empty (line 38), and invokes notifyAll() (line 41) to signal any and all waiting threads that something has changed.

CubbyHoleMain (see Listing 8.10) creates an instance of CubbyHole and starts two threads to interact with it.

Example 8.10. CubbyHoleMain.java—Used to Demonstrate CubbyHole

 1: public class CubbyHoleMain extends Object {
 2:     private static void print(String msg) {
 3:         String name = Thread.currentThread().getName();
 4:         System.out.println(name + ": " + msg);
 5:     }
 6:
 7:     public static void main(String[] args) {
 8:         final CubbyHole ch = new CubbyHole();
 9:
10:         Runnable runA = new Runnable() {
11:                 public void run() {
12:                     try {
13:                         String str;
14:                         Thread.sleep(500);
15:
16:                         str = "multithreaded";
17:                         ch.putIn(str);
18:                         print("in run() - just put in: '" +
19:                                 str + "'");
20:
21:                         str = "programming";
22:                         ch.putIn(str);
23:                         print("in run() - just put in: '" +
24:                                 str + "'");
25:
26:                         str = "with Java";
27:                         ch.putIn(str);
28:                         print("in run() - just put in: '" +
29:                                 str + "'");
30:                     } catch ( InterruptedException x ) {
31:                         x.printStackTrace();
32:                     }
33:                 }
34:             };
35:
36:         Runnable runB = new Runnable() {
37:                 public void run() {
38:                     try {
39:                         Object obj;
40:
41:                         obj = ch.takeOut();
42:                         print("in run() - just took out: '" +
43:                                 obj + "'");
44: 
45:                         Thread.sleep(500);
46:
47:                         obj = ch.takeOut();
48:                         print("in run() - just took out: '" +
49:                                 obj + "'");
50:
51:                         obj = ch.takeOut();
52:                         print("in run() - just took out: '" +
53:                                 obj + "'");
54:                     } catch ( InterruptedException x ) {
55:                         x.printStackTrace();
56:                     }
57:                 }
58:             };
59:
60:         Thread threadA = new Thread(runA, "threadA");
61:         threadA.start();
62:
63:         Thread threadB = new Thread(runB, "threadB");
64:         threadB.start();
65:     }
66: }

In the main() method (lines 7–65), an instance of CubbyHole is constructed and assigned to ch (line 8). threadA initially sleeps for 0.5 seconds to give threadB a chance to get going (line 14). threadA then proceeds to invoke putIn() three times in a row. Three String objects are passed one at a time into putIn(): "multithreaded", "programming", and "with Java" (lines 17, 22, and 27). CubbyHole can hold only one item at a time, so the second and third calls to putIn() may block waiting for threadB to remove items.

When threadB is started, it immediately invokes takeOut() (line 41) and blocks waiting for threadA to add something. After removing the first item, threadB sleeps for 0.5 seconds (line 45) to give threadA time to put the second item into slot and to block waiting to put in the third item. threadB then proceeds to take out the second and third items (lines 47, 51).

Listing 8.11 shows the output from a particular run of CubbyHoleMain. Because of thread-scheduling issues, your output might differ a little.

Example 8.11. Possible Output from CubbyHoleMain

 1: threadB: in takeOut() - entering
 2: threadB: in takeOut() - empty, about to wait()
 3: threadA: in putIn() - entering
 4: threadA: in putIn() - filled slot, about to notifyAll()
 5: threadA: in putIn() - leaving
 6: threadA: in run() - just put in: 'multithreaded'
 7: threadA: in putIn() - entering
 8: threadA: in putIn() - occupied, about to wait()
 9: threadB: in takeOut() - notified, back from wait()
10: threadB: in takeOut() - emptied slot, about to notifyAll()
11: threadB: in takeOut() - leaving
12: threadB: in run() - just took out: 'multithreaded'
13: threadA: in putIn() - notified, back from wait()
14: threadA: in putIn() - filled slot, about to notifyAll()
15: threadA: in putIn() - leaving
16: threadA: in run() - just put in: 'programming'
17: threadA: in putIn() - entering
18: threadA: in putIn() - occupied, about to wait()
19: threadB: in takeOut() - entering
20: threadB: in takeOut() - emptied slot, about to notifyAll()
21: threadB: in takeOut() - leaving
22: threadB: in run() - just took out: 'programming'
23: threadB: in takeOut() - entering
24: threadB: in takeOut() - empty, about to wait()
25: threadA: in putIn() - notified, back from wait()
26: threadA: in putIn() - filled slot, about to notifyAll()
27: threadA: in putIn() - leaving
28: threadA: in run() - just put in: 'with Java'
29: threadB: in takeOut() - notified, back from wait()
30: threadB: in takeOut() - emptied slot, about to notifyAll()
31: threadB: in takeOut() - leaving
32: threadB: in run() - just took out: 'with Java'

Notice that sometimes a thread enters one of the methods and does not have to wait (lines 3–4), and other times it does have to wait (lines 1–2). This output shows both the case of a thread blocking waiting to put an item in (lines 7–8) and the case of a thread blocking waiting for an item to remove (lines 23–24). Even with all this complexity, the three String objects are delivered safely through the CubbyHole in the exact order that they were added.

Figure 8.4 shows the timeline of events from this run of CubbyHoleMain. The diagram reflects the internal workings that occurred to produce the output in Listing 8.11. Notice that both threads invoke wait() and notifyAll(). Both threads signal and listen for signals. Notice that to add and remove three items required 10 lock-unlock cycles on this. This kind of complexity is required to safely ensure that there are not any race conditions, missed notifications, or early notifications.

Timeline of events from a particular run of CubbyHoleMain.

Figure 8.4. Timeline of events from a particular run of CubbyHoleMain.

Note

The basic premise behind CubbyHole can be expanded into a First-In-First-Out (FIFO) queue structure. In Chapter 18, "First-In-First-Out (FIFO) Queue," I'll show you a technique for creating a FIFO queue. A FIFO queue with a capacity to hold exactly one item can be very useful for signaling between threads because it nicely encapsulates the complexity of the wait/notify mechanism.

Using join() to Wait for a Thread to Die

The join() method of Thread can be used to cause the current thread to block waiting for the specified thread to die. This is a relatively crude form of inter-thread communication, but on occasion it can be useful. If threadX runs the code

try {
    threadY.join()
} catch ( InterruptedException x ) {
}

threadX will block waiting for threadY to die. If threadX is interrupted while inside join(), it will throw an InterruptedException. There are three versions of the join() method available in Thread, all of which are public: join(), join(long), and join(long, int). Additionally, none of these methods can be overridden in a subclass because they are all declared final.

join()

public final void join()
        throws InterruptedException

The join() method causes the current thread to block and wait an unlimited amount of time for this thread to die. The current thread will throw an InterruptedException if interrupted while waiting for the specified thread to die.

join(long)

public final synchronized void join(long msTimeout)
        throws InterruptedException,
               IllegalArgumentException      // RuntimeException

The join(long) method causes the current thread to block and wait up to msTimeout milliseconds for the specified thread to die. If msTimeout is 0, the current thread will never time out and will wait forever for the specified thread to die (just like join()). If msTimeout is less than 0, an IllegalArgumentException is thrown. The current thread will throw an InterruptedException if interrupted while waiting for the specified thread to die.

join(long, int)

public final synchronized void join(long msTimeout, int nanoSec)
        throws InterruptedException,
               IllegalArgumentException      // RuntimeException

The join(long, int) method works just like join(long) (see above) with the exception that nanoseconds can be added to the timeout value. The argument nanoSec is added to msTimeout to determine the total amount of time that the thread will wait for the specified thread to die before returning. An IllegalArgumentException is thrown if msTimeout is less than 0 or if nanoSec is less than 0 or greater than 999999. The current thread will throw an InterruptedException if interrupted while waiting for the specified thread to die.

JoinDemo

The class JoinDemo (see Listing 8.12) demonstrates how join() can be used to wait for threads to die. The main thread spawns three new threads and then waits for each of them to die. Each of the threads lives for a different amount of time.

Example 8.12. JoinDemo.java—Demonstration of the Use of join()

 1: public class JoinDemo extends Object {
 2:     public static Thread launch(String name, long napTime) {
 3:         final long sleepTime = napTime;
 4:
 5:         Runnable r = new Runnable() {
 6:                 public void run() {
 7:                     try {
 8:                         print("in run() - entering");
 9:                         Thread.sleep(sleepTime);
10:                     } catch ( InterruptedException x ) {
11:                         print("interrupted!");
12:                     } finally {
13:                         print("in run() - leaving");
14:                     }
15:                 }
16:             };
17:
18:         Thread t = new Thread(r, name);
19:         t.start();
20:
21:         return t;
22:     }
23:
24:     private static void print(String msg) {
25:         String name = Thread.currentThread().getName();
26:         System.out.println(name + ": " + msg);
27:     }
28:
29:     public static void main(String[] args) {
30:         Thread[] t = new Thread[3];
31:
32:         t[0] = launch("threadA", 2000);
33:         t[1] = launch("threadB", 1000);
34:         t[2] = launch("threadC", 3000);
35:
36:         for ( int i = 0; i < t.length; i++ ) {
37:             try {
38:                 String idxStr = "t[" + i + "]";
39:                 String name = "[" + t[i].getName() + "]";
40: 
41:                 print(idxStr + ".isAlive()=" +
42:                         t[i].isAlive() + " " + name);
43:                 print("about to do: " + idxStr +
44:                         ".join() " + name);
45:
46:                 long start = System.currentTimeMillis();
47:                 t[i].join(); // wait for the thread to die
48:                 long stop = System.currentTimeMillis();
49:
50:                 print(idxStr + ".join() - took " +
51:                         ( stop - start ) + " ms " + name);
52:             } catch ( InterruptedException x ) {
53:                 print("interrupted waiting on #" + i);
54:             }
55:         }
56:     }
57: }

In the static method launch() (lines 2–22) of JoinDemo, a new Runnable instance r is created. Inside the run() method of r, the thread prints an announcement (line 8), sleeps for the specified delay (line 9), and prints another message just before leaving run() (line 13). A new Thread is constructed and started for r and is given the name passed into launch() (lines 18–19). A reference to this new running Thread is returned to the caller (line 21).

In main() (lines 29–56), a Thread[] named t is created to hold three references (line 30). The launch() method is called three times with different parameters and the references returned are stored into t (lines 32–34):

  • t[0] is named threadA and sleeps two seconds before dying.

  • t[1] is named threadB and sleeps one seconds before dying.

  • t[2] is named threadC and sleeps three seconds before dying.

All three threads are running concurrently. threadB finishes first, threadA finishes second, and threadC finishes last.

After launching all three threads, the main thread continues on into the for loop (lines 36–55). In this loop, main prints out some diagnostic information for each of the launched threads and then invokes join() on each of them to wait for each to die (line 47). Without all the extra information gathering and printing, the for loop boils down to this:

for ( int i = 0; i < t.length; i++ ) {
    try {
        t[i].join(); // wait for the thread to die
    } catch ( InterruptedException x ) {
    }
}

The main thread blocks for about two seconds waiting for threadA to die. Meanwhile, threadB has already died, so when the main thread invokes join() on threadB, join() returns right away. The main thread then proceeds to block for about one second waiting for threadC to die.

Listing 8.13 shows the output produced from a particular run of JoinDemo. Your output should match fairly closely. The only differences should be a little variation in the number of milliseconds that the main thread spends inside join() and perhaps a few lines of output swapped with each other.

Example 8.13. Output from JoinDemo

 1: main: t[0].isAlive()=true [threadA]
 2: threadA: in run() - entering
 3: threadB: in run() - entering
 4: threadC: in run() - entering
 5: main: about to do: t[0].join() [threadA]
 6: threadB: in run() - leaving
 7: threadA: in run() - leaving
 8: main: t[0].join() - took 1920 ms [threadA]
 9: main: t[1].isAlive()=false [threadB]
10: main: about to do: t[1].join() [threadB]
11: main: t[1].join() - took 0 ms [threadB]
12: main: t[2].isAlive()=true [threadC]
13: main: about to do: t[2].join() [threadC]
14: threadC: in run() - leaving
15: main: t[2].join() - took 990 ms [threadC]

The main thread finds threadA still alive (line 1), invokes join() on it (line 5), and waits 1920 milliseconds for it to die (line 8). Notice that threadB reported that it was leaving its run() method while the main thread was waiting on threadA (line 6). Therefore, the main thread finds threadB already dead (line 9) and when join() is invoked (line 10), the main thread returns right away (line 11). Next, the main thread finds threadC still alive (line 12), invokes join() on it (line 13), and waits 990 milliseconds for it to die (line 15).

Streaming Data Between Threads Using Pipes

The java.io package provides many classes for writing and reading data to and from streams. Most of the time, the data is written to or read from a file or network connection. Instead of streaming data to a file, a thread can stream it through a pipe to another thread. The first thread writes to the pipe, and the second thread reads from the pipe. A pipe is neither a file nor a network connection, but a structure in memory that holds the data that is written until it is read. Usually, a pipe has a fixed capacity. When the pipe is filled to this capacity, attempts to write more data will block waiting until some data is drained (read) from the pipe by another thread. Similarly, when a pipe is empty, attempts to read data from the pipe will block waiting until another thread writes some data into it.

There are four pipe-related classes in the java.io package that can be used to stream data between threads: PipedInputStream, PipedOutputStream, PipedReader, and PipedWriter. A PipedInputStream and a PipedOutputStream are hooked together to transfer bytes between threads. A PipedReader and a PipedWriter are hooked together to transfer character data between threads. Figure 8.5 shows the class diagram for these classes. The PipedOutputStream object keeps a reference to the PipedInputStream object it is connected to. Similarly, the PipedWriter object keeps a reference to the PipedReader object it is connected to.

A pipe made up of a PipedInputStream and a PipedOutputStream has a capacity to hold 1024 bytes. This means that the thread doing the writing can be up to 1024 bytes ahead of the thread doing the reading. This buffering makes the transfer of data more efficient than a single-byte handoff would be. A pipe made up of a PipedReader and a PipedWriter has a capacity to hold 1024 characters. Again, this buffering allows the thread doing the writing to work a little bit ahead of the thread doing the reading. I discovered the size of the pipes (1024 bytes and 1024 characters) by examining the source code from Sun Microsystems. The API documentation gives no information or guarantees regarding the internal pipe size. Therefore, you should not depend on 1024 being the universal size.

Class diagram for the pipe-related classes in java.io.

Figure 8.5. Class diagram for the pipe-related classes in java.io.

PipedInputStream and PipedOutputStream each represent an end of the pipe and need to be connected to each other before data can be sent. Both PipedInputStream and PipedOutputStream have a constructor that takes a reference to the other. It doesn't matter which is constructed first. You can write either

PipedInputStream pipeIn = new PipedInputStream();
PipedOutputStream pipeOut = new PipedOutputStream(pipeIn);

or

PipedOutputStream pipeOut = new PipedOutputStream();
PipedInputStream pipeIn = new PipedInputStream(pipeOut);

Additionally, both ends can be created with their zero-argument constructors and connected together with connect(). You can write either

PipedInputStream pipeIn = new PipedInputStream();
PipedOutputStream pipeOut = new PipedOutputStream();
pipeIn.connect(pipeOut);

or

PipedInputStream pipeIn = new PipedInputStream();
PipedOutputStream pipeOut = new PipedOutputStream();
pipeOut.connect(pipeIn);

If the ends of the pipe are not yet connected to each other, any attempt to read or write will cause an IOException to be thrown. Because of this, it's generally a good idea to connect the ends right away by using the constructor. PipedReader and PipedWriter connect to each other in the same ways that PipedInputStream and PipedOutputStream do, so the same rules and guidelines apply.

PipedBytes

The PipedBytes class (see Listing 8.14) shows how data can be sent through a pipe from one thread to another. Integers are written to a PipedOutputStream by one thread and are read from a PipedInputStream by another thread.

Example 8.14. PipedBytes.java—Sending Data Between Threads Using PipedInputStream and PipedOutputStream

 1: import java.io.*;
 2:
 3: public class PipedBytes extends Object {
 4:     public static void writeStuff(OutputStream rawOut) {
 5:         try {
 6:             DataOutputStream out = new DataOutputStream(
 7:                     new BufferedOutputStream(rawOut));
 8:    
 9:             int[] data = { 82, 105, 99, 104, 97, 114, 100, 32,
10:                            72, 121, 100, 101 } ;
11:
12:             for ( int i = 0; i < data.length; i++ ) {
13:                 out.writeInt(data[i]);
14:             }
15:
16:             out.flush();
17:             out.close();
18:         } catch ( IOException x ) {
19:             x.printStackTrace();
20:         }
21:     }
22:
23:     public static void readStuff(InputStream rawIn) {
24:         try {
25:             DataInputStream in = new DataInputStream(
26:                     new BufferedInputStream(rawIn));
27:
28:             boolean eof = false;
29:             while ( !eof ) {
30:                 try {
31:                     int i = in.readInt();
32:                     System.out.println("just read: " + i);
33:                 } catch ( EOFException eofx ) {
34:                     eof = true;
35:                 }
36:             }
37:
38:             System.out.println("Read all data from the pipe");
39:         } catch ( IOException x ) {
40:             x.printStackTrace();
41:         }
42:     }
43: 
44:     public static void main(String[] args) {
45:         try {
46:             final PipedOutputStream out =
47:                     new PipedOutputStream();
48:
49:             final PipedInputStream in =
50:                     new PipedInputStream(out);
51:
52:             Runnable runA = new Runnable() {
53:                     public void run() {
54:                         writeStuff(out);
55:                     }
56:                 } ;
57:
58:             Thread threadA = new Thread(runA, "threadA");
59:             threadA.start();
60:
61:             Runnable runB = new Runnable() {
62:                     public void run() {
63:                         readStuff(in);
64:                     }
65:                 };
66:
67:             Thread threadB = new Thread(runB, "threadB");
68:             threadB.start();
69:         } catch ( IOException x ) {
70:             x.printStackTrace();
71:         }
72:     }
73: }

In the main() method (lines 44–72), a data pipe is created and two threads are started to transfer data through it. First, the PipedOutputStream is constructed (lines 46–47). Next, the associated PipedInputStream is created by passing a reference to the PipedOutputStream that makes up the other end of the pipe (lines 49–50). threadA is started and executes the writeStuff() method, passing in a reference to the PipedOutputStream (line 54). threadB is started and executes the readStuff() method, passing in a reference to the PipedInputStream (line 63). threadA is writing data into one end of the pipe while threadB is simultaneously reading data from the other end of the pipe.

The writeStuff() method (lines 4–21) only expects an OutputStream to be passed to it and makes no special considerations for the fact that it might just happen to be a PipedOutputStream (line 4). When threadA invokes writeStuff(), it wraps the OutputStream in a DataOuptutStream to be able to use the writeInt() method to put the integers into the pipe (lines 6–7). The integer data to be sent is stored into the int[] referred to by data (lines 9–10). Each of the integers in the array is written to the DataOutputStream (lines 12–14). Before returning, the stream is flushed and closed (lines 16–17).

Like writeStuff(), the readStuff() method (lines 23–42) only expects an InputStream to be passed to it and makes no special considerations for the fact that it might happen to be a PipedInputStream (line 23). This raw InputStream is wrapped in a DataInputStream to facilitate the reading of integers (lines 25–26). As long as the end-of-file is not detected, threadB continues to read integers from the stream (lines 28–36).

Listing 8.15 shows the output produced when PipedBytes is run. Your output should match. Notice that all the integers are read by threadB in exactly the same order as they are written by threadA.

Example 8.15. Output from PipedBytes

 1: just read: 82
 2: just read: 105
 3: just read: 99
 4: just read: 104
 5: just read: 97
 6: just read: 114
 7: just read: 100
 8: just read: 32
 9: just read: 72
10: just read: 121
11: just read: 100
12: just read: 101
13: Read all data from the pipe

PipedCharacters

The PipedCharacters class (see Listing 8.16) shows how character-based data can be sent through a pipe from one thread to another. Strings are written to a PipedWriter by one thread and are read from a PipedReader by another thread.

Example 8.16. PipedCharacters.java—Sending Characters Between Threads Using PipedReader and PipedWriter

 1: import java.io.*;
 2:
 3: public class PipedCharacters extends Object {
 4:     public static void writeStuff(Writer rawOut) {
 5:         try {
 6:             BufferedWriter out = new BufferedWriter(rawOut);
 7:
 8:             String[][] line = {
 9:                     { "Java", "has", "nice", "features." },
10:                     { "Pipes", "are", "interesting." },
11:                     { "Threads", "are", "fun", "in", "Java." },
12:                     { "Don't", "you", "think", "so?" }
13:                 };
14:
15:             for ( int i = 0; i < line.length; i++ ) {
16:                 String[] word = line[i];
17:
18:                 for ( int j = 0; j < word.length; j++ ) {
19:                     if ( j > 0 ) {
20:                         // put a space between words
21:                         out.write(" ");
22:                     } 
23:
24:                     out.write(word[j]);
25:                 }
26:
27:                 // mark the end of a line
28:                 out.newLine();
29:             }
30:
31:             out.flush();
32:             out.close();
33:         } catch ( IOException x ) {
34:             x.printStackTrace();
35:         }
36:     }
37:
38:     public static void readStuff(Reader rawIn) {
39:         try {
40:             BufferedReader in = new BufferedReader(rawIn);
41: 
42:             String line;
43:             while ( ( line = in.readLine() ) != null ) {
44:                 System.out.println("read line: " + line);
45:             }
46:
47:             System.out.println("Read all data from the pipe");
48:         } catch ( IOException x ) {
49:             x.printStackTrace();
50:         }
51:     }
52:
53:     public static void main(String[] args) {
54:         try {
55:             final PipedWriter out = new PipedWriter();
56:
57:             final PipedReader in = new PipedReader(out);
58:
59:             Runnable runA = new Runnable() {
60:                     public void run() {
61:                         writeStuff(out);
62:                     }
63:                 };
64:
65:             Thread threadA = new Thread(runA, "threadA");
66:             threadA.start();
67:    
68:             Runnable runB = new Runnable() {
69:                     public void run() {
70:                         readStuff(in);
71:                     }
72:                 };
73:    
74:             Thread threadB = new Thread(runB, "threadB");
75:             threadB.start();
76:         } catch ( IOException x ) {
77:             x.printStackTrace();
78:         }
79:     }
80: }

In the main() method (lines 53–79), a character-based data pipe is created and two threads are started to transfer data through it. First, a PipedWriter is constructed (line 55). Next, the associated PipedReader is constructed by passing a reference to the PipedWriter (line 57). threadA is started and executes the writeStuff() method, passing in a reference to the PipedWriter (line 61). threadB is started and executes the readStuff() method, passing in a reference to the PipedReader (line 70). threadA is writing characters into one end of the pipe while threadB is simultaneously reading characters from the other end of the pipe.

The writeStuff() method (lines 4–36) only expects a Writer to be passed to it and makes no special considerations for the fact that it might be a PipedWriter (line 4). When threadA invokes writeStuff(), it wraps the Writer in a BufferedWriter to be able to use the newLine() method to mark the end of each line (line 6). The sentences to be sent are stored in the String[][] referred to by line (lines 8–13). The two-dimensional array stores the sentences in one dimension and the individual words that make up each sentence in the other dimension. Each line (sentence) is stepped through using the outer for loop (lines 15–29). Each word in a line is stepped through using the inner for loop (lines 18–25). After the last word of each line is written, the newLine() method is used to mark the end-of-line (line 28). Before returning, the BufferedWriter is flushed and closed (lines 31–32).

The readStuff() method (lines 38–51) only expects a Reader to be passed to it and makes no special considerations for the fact that it might happen to be a PipedReader (line 38). This raw Reader is wrapped in a BufferedReader to facilitate the reading of whole lines at a time (line 40). Each line is read from the BufferedReader until the end-of-file is detected by a null return from readLine() (lines 42–45).

Listing 8.17 shows the output produced when PipedCharacters is run. Your output should match. Notice that all the lines (sentences) are read by threadB in exactly the same order that they are written by threadA.

Example 8.17. Output from PipedCharacters

1: read line: Java has nice features.
2: read line: Pipes are interesting.
3: read line: Threads are fun in Java.
4: read line: Don't you think so?
5: Read all data from the pipe

Using ThreadLocal and InheritableThreadLocal

Support for thread-specific variables has been added as of release 1.2 of the JDK. The value returned from the get() method ThreadLocal depends on which thread invokes the method. InheritableThreadLocal allows these values to be inherited from parent to child thread.

I'll just give you a quick, high-level overview on how the thread-specific variables are implemented. Figure 8.6 shows the class relationships for ThreadLocal and InheritableThreadLocal. ThreadLocal contains a reference to a WeakHashMap that holds key-value pairs. Weak references were introduced in JDK 1.2, and WeakHashMap takes advantage of them to automatically remove mappings for threads that have died and been de-referenced in all other places. This way, ThreadLocal does not keep track of values for threads that have long since died. In the WeakHashMap, the lookup key is the reference to the Thread and the value stored is a ThreadLocal.Entry object. ThreadLocal.Entry is an inner class to ThreadLocal and is used by ThreadLocal to store the thread-specific values.

Class relationships for ThreadLocal and InheritableThreadLocal.

Figure 8.6. Class relationships for ThreadLocal and InheritableThreadLocal.

InheritableThreadLocal is a subclass of ThreadLocal that provides a mechanism for the thread-specific variable to be inherited from parent thread to child thread. InheritableThreadLocal.Entry is a subclass of ThreadLocal.Entry and is also an inner class. Thread contains a private reference to an InheritableThreadLocal.Entry object and uses it to pass the thread-specific variable down from parent thread to child thread when a new thread is created.

ThreadLocal API

ThreadLocal has two public methods. The first one is get():

public Object get()

get() is used to retrieve the thread-specific value. Internally, it looks up to see if the calling thread has a value stored. If is does, it returns that value. If not, it calls the protected method initialValue() to initialize a value for the calling thread, stores it in the internal WeakHashMap for future lookups, and returns the value to the caller. Typically, get() is the only method called on a ThreadLocal object.

However, on rare occasions, a thread can set its own value for future use by invoking the other public method of ThreadLocal directly:

public void set(Object value)

set() takes the value passed and stores it in the internal WeakHashMap for future lookups.

ThreadLocal is not abstract, but it generally needs to be subclassed to be useful. This protected method should be overridden in the subclass:

protected Object initialValue()

By default, initialValue() returns null, but in the subclass it can return a more meaningful value.

ThreadID

The class ThreadID (see Listing 8.18) is a subclass of ThreadLocal and creates a unique ID for every thread that invokes get(). If a thread comes back and invokes get() again, the same value is returned.

Example 8.18. ThreadID.java—Using ThreadLocal to Generate Unique Thread IDs

 1: public class ThreadID extends ThreadLocal {
 2:     private int nextID;
 3:
 4:     public ThreadID() {
 5:         nextID = 10001;
 6:     }
 7:
 8:     private synchronized Integer getNewID() {
 9:         Integer id = new Integer(nextID);
10:         nextID++;
11:         return id;
12:     }
13:
14:     // override ThreadLocal's version
15:     protected Object initialValue() {
16:         print("in initialValue()");
17:         return getNewID();
18:     }
19:
20:     public int getThreadID() {
21:         // Call get() in ThreadLocal to get the calling
22:         // thread's unique ID.
23:         Integer id = (Integer) get();
24:         return id.intValue();
25:     }
26:
27:     private static void print(String msg) {
28:         String name = Thread.currentThread().getName();
29:         System.out.println(name + ": " + msg);
30:     }
31: }

ThreadID is a subclass of ThreadLocal (line 1) and creates a unique ID for every thread that invokes the get() method. The next unique ID to be assigned is held in nextID (line 2) and is initialized to be 10001 (line 5). The getNewID() method (lines 8–12) is synchronized to ensure that only one thread is inside it at a time. The initialValue() method (lines 15–18) overrides the superclass's method and calls getNewID() to generate a new, unique ID.

The only public method is getThreadID() (lines 20–25). Inside, get() is invoked to look up the value for the calling thread (line 23). This action might indirectly cause initialValue() to be called if it is the first time that the calling thread has invoked get().

The class ThreadIDMain (see Listing 8.19) starts up three threads to demonstrate how ThreadID works. Each thread calls getThreadID() twice to show that the first call for each thread generates a new ID and that the second call simply returns the same value generated during the first call.

Example 8.19. ThreadIDMain.java—Used to Demonstrate ThreadID

 1: public class ThreadIDMain extends Object implements Runnable {
 2:     private ThreadID var;
 3:
 4:     public ThreadIDMain(ThreadID var) {
 5:         this.var = var;
 6:     }
 7:
 8:     public void run() {
 9:         try { 
10:             print("var.getThreadID()=" + var.getThreadID());
11:             Thread.sleep(2000);
12:             print("var.getThreadID()=" + var.getThreadID());
13:         } catch ( InterruptedException x ) {
14:             // ignore
15:         }
16:     }
17:
18:     private static void print(String msg) {
19:         String name = Thread.currentThread().getName();
20:         System.out.println(name + ": " + msg);
21:     }
22:
23:     public static void main(String[] args) {
24:         ThreadID tid = new ThreadID();
25:         ThreadIDMain shared = new ThreadIDMain(tid);
26:
27:         try {
28:             Thread threadA = new Thread(shared, "threadA");
29:             threadA.start();
30:
31:             Thread.sleep(500);
32:
33:             Thread threadB = new Thread(shared, "threadB");
34:             threadB.start();
35:
36:             Thread.sleep(500);
37:
38:             Thread threadC = new Thread(shared, "threadC");
39:             threadC.start();
40:         } catch ( InterruptedException x ) {
41:             // ignore
42:         }
43:     }
44: }

In main() (lines 23–43), one instance of ThreadID is constructed (line 24) and passed into the constructor for ThreadIDMain (line 25). The instance of ThreadIDMain is referred to by shared and implements the Runnable interface. The main thread proceeds to create three new threads (threadA, threadB, and threadC) that will all simultaneously run the one instance referenced by shared (lines 28–39).

When each thread runs, the thread invokes the run() method (lines 8–16). Inside run(), getThreadID() is invoked and the result is printed (line 10). After sleeping for two seconds (line 11), getThreadID() is invoked again and the result is printed (line 12).

Listing 8.20 shows the output produced when ThreadIDMain is run. Your output should match.

Example 8.20. Output from ThreadIDMain

1: threadA: in initialValue()
2: threadA: var.getThreadID()=10001
3: threadB: in initialValue()
4: threadB: var.getThreadID()=10002
5: threadC: in initialValue()
6: threadC: var.getThreadID()=10003
7: threadA: var.getThreadID()=10001
8: threadB: var.getThreadID()=10002
9: threadC: var.getThreadID()=10003

When threadA invokes getThreadID(), it indirectly causes initialValue() to be called (line 1). The unique ID for threadA is 10001 (line 2). When threadB and threadC invoke getThreadID(), they both get a new unique ID (lines 3–6). The second time that each of the threads invokes getThreadID(), a unique ID does not have to be generated and the same ID that was returned the first time is returned again (lines 7–9).

InheritableThreadLocal API

InheritableThreadLocal is a subclass of ThreadLocal and allows a thread-specific value to be inherited from the parent thread to the child thread. There are not any public methods on InheritableThreadLocal. It can be used directly as a special kind of ThreadLocal that passes its value from parent thread to child thread.

If you don't want to use the parent thread's value directly, you can override

protected Object childValue(Object parentValue)

to produce a customized child value at the time that the child thread is created. By default, childValue() simply returns parentValue.

InheritableThreadID

The class InheritableThreadID (see Listing 8.21) demonstrates three different ways that thread-specific variables can behave regarding inheritance from parent thread to child thread. First, a ThreadLocal variable is used to demonstrate that the child thread will have a different thread-specific value than its parent thread does. Second, an InheritableThreadLocal is used without overriding childValue() to demonstrate that the child thread will have the exact same thread-specific value as its parent. Third, an InheritableThreadLocal is used with the childValue() method overridden to demonstrate that the child's value can be based on the parent's value.

Example 8.21. InheritableThreadID.java—Demonstration of InheritableThreadLocal

  1: public class InheritableThreadID extends Object {
  2:     public static final int UNIQUE  = 101;
  3:     public static final int INHERIT = 102;
  4:     public static final int SUFFIX  = 103;
  5:
  6:     private ThreadLocal threadLocal;
  7:     private int nextID;
  8:
  9:     public InheritableThreadID(int type) {
 10:         nextID = 201;
 11:
 12:         switch ( type ) {
 13:             case UNIQUE:
 14:                 threadLocal = new ThreadLocal() {
 15:                         // override from ThreadLocal
 16:                         protected Object initialValue() {
 17:                             print("in initialValue()");
 18:                             return getNewID();
 19:                         }
 20:                     };
 21:                 break;
 22:
 23:             case INHERIT:
 24:                 threadLocal = new InheritableThreadLocal() {
 25:                         // override from ThreadLocal
 26:                         protected Object initialValue() {
 27:                             print("in initialValue()");
 28:                             return getNewID();
 29:                         }
 30:                     };
 31:                 break;
 32:
 33:             case SUFFIX:
 34:                 threadLocal = new InheritableThreadLocal() {
 35:                         // override from ThreadLocal
 36:                         protected Object initialValue() {
 37:                             print("in initialValue()");
 38:                             return getNewID();
 39:                         }
 40: 
 41:                         // override from InheritableThreadLocal
 42:                         protected Object childValue(
 43:                                     Object parentValue
 44:                                 ) {
 45:
 46:                             print("in childValue() - " +
 47:                                 "parentValue=" + parentValue);
 48:
 49:                             return parentValue + "-CH";
 50:                         }
 51:                     } ;
 52:                 break;
 53:             default:
 54:                 break;
 55:         }
 56:     }
 57:
 58:     private synchronized String getNewID() {
 59:         String id = "ID" + nextID;
 60:         nextID++;
 61:         return id;
 62:     }
 63:
 64:     public String getID() {
 65:         return (String) threadLocal.get();
 66:     }
 67:
 68:     public static void print(String msg) {
 69:         String name = Thread.currentThread().getName();
 70:         System.out.println(name + ": " + msg);
 71:     }
 72:
 73:     public static Runnable createTarget(InheritableThreadID id) {
 74:         final InheritableThreadID var = id;
 75:
 76:         Runnable parentRun = new Runnable() {
 77:             public void run() {
 78:                 print("var.getID()=" + var.getID());
 79:                 print("var.getID()=" + var.getID());
 80:                 print("var.getID()=" + var.getID());
 81: 
 82:                 Runnable childRun = new Runnable() {
 83:                         public void run() {
 84:                             print("var.getID()=" + var.getID());
 85:                             print("var.getID()=" + var.getID());
 86:                             print("var.getID()=" + var.getID());
 87:                         }
 88:                     };
 89:
 90:                 Thread parentT = Thread.currentThread();
 91:                 String parentName = parentT.getName();
 92:                 print("creating a child thread of " +
 93:                     parentName);
 94:
 95:                 Thread childT = new Thread(childRun,
 96:                         parentName + "-child");
 97:                 childT.start();
 98:             }
 99:         };
100:
101:         return parentRun;
102:     }
103:
104:     public static void main(String[] args) {
105:         try {
106:             System.out.println("======= ThreadLocal =======");
107:             InheritableThreadID varA =
108:                 new InheritableThreadID(UNIQUE);
109:
110:             Runnable targetA = createTarget(varA);
111:             Thread threadA = new Thread(targetA, "threadA");
112:             threadA.start();
113:
114:             Thread.sleep(2500);
115:             System.out.println("
======= " +
116:                 "InheritableThreadLocal =======");
117:
118:             InheritableThreadID varB =
119:                 new InheritableThreadID(INHERIT);
120:
121:             Runnable targetB = createTarget(varB);
122:             Thread threadB = new Thread(targetB, "threadB");
123:             threadB.start();
124: 
125:             Thread.sleep(2500);
126:             System.out.println("
======= " +
127:                 "InheritableThreadLocal - custom childValue()" +
128:                 " =======");
129:
130:             InheritableThreadID varC =
131:                 new InheritableThreadID(SUFFIX);
132:
133:             Runnable targetC = createTarget(varC);
134:             Thread threadC = new Thread(targetC, "threadC");
135:             threadC.start();
136:         } catch ( InterruptedException x ) {
137:             // ignore
138:         }
139:
140:     }
141: }

In the constructor for InheritableThreadID (lines 9–56), one of three different ThreadLocal instances is created. If type is UNIQUE (line 13), a plain ThreadLocal is used with its initialValue() method overridden to call getNewID() (lines 16–19). If type is INHERIT, an InheritableThreadLocal is used with its initialValue() method overridden to call getNewID() (lines 26–29). If type is SUFFIX, an InheritableThreadLocal is used with its initialValue() method overridden to call getNewID() (lines 36–39) and its childValue() method overridden to return the parent's value with "-CH" appended to it (lines 42–50).

Every time the synchronized method getNewID() is called, it generates a new String-based ID and returns it (lines 58–62).

When the getID() method (lines 64–66) is called, it invokes the get() method on whichever one of the three types of ThreadLocal variables was created in the constructor.

The static method createTarget() (lines 73–102) is used by main() to create a Runnable. When run() is invoked, the getID() method for the particular instance of InheritableThreadID is invoked three times (lines 78–80). Then this parent thread creates a new child thread (lines 95–97). The child thread is then started and invokes getID() three more times to see what value is returned to the child thread (lines 84–86).

In the main() method (lines 104–140), three different instances of InheritableThreadID are constructed and three different threads work with them.

First, varA refers to an InheritableThreadID that simply creates a unique ID for every thread—regardless of parent-child relationships (lines 107–108). A Runnable is created to use varA (line 110), and threadA is started to run it (lines 111–112).

Second, varB refers to an InheritableThreadID that allows the parent thread to pass its ID unmodified to the child thread—the child inherits the parent's value (lines 118–119). A Runnable is created to use varB (line 121), and threadB is started to run it (lines 122–123).

Third, varC refers to an InheritableThreadID that intercepts the passing of the value from the parent to child and adds a suffix to the child's value (lines 130–131). A Runnable is created to use varC (line 133), and threadC is started to run it (lines 134–135).

Listing 8.22 shows the output produced when InheritableThreadID is run. Your output should match.

Example 8.22. Output Produced When InheritableThreadID Is Run

 1: ======= ThreadLocal =======
 2: threadA: in initialValue()
 3: threadA: var.getID()=ID201
 4: threadA: var.getID()=ID201
 5: threadA: var.getID()=ID201
 6: threadA: creating a child thread of threadA
 7: threadA-child: in initialValue()
 8: threadA-child: var.getID()=ID202
 9: threadA-child: var.getID()=ID202
10: threadA-child: var.getID()=ID202
11:
12: ======= InheritableThreadLocal =======
13: threadB: in initialValue()
14: threadB: var.getID()=ID201
15: threadB: var.getID()=ID201
16: threadB: var.getID()=ID201
17: threadB: creating a child thread of threadB
18: threadB-child: var.getID()=ID201
19: threadB-child: var.getID()=ID201
20: threadB-child: var.getID()=ID201
21:
22: ======= InheritableThreadLocal - custom childValue() =======
23: threadC: in initialValue()
24: threadC: var.getID()=ID201
25: threadC: var.getID()=ID201
26: threadC: var.getID()=ID201
27: threadC: creating a child thread of threadC
28: threadC: in childValue() - parentValue=ID201
29: threadC-child: var.getID()=ID201-CH
30: threadC-child: var.getID()=ID201-CH
31: threadC-child: var.getID()=ID201-CH

Notice that when UNIQUE is used, the parent and child have completely different ID values: ID201 and ID202 (lines 1–10). When INHERIT is used, the parent and child have exactly the same ID values: ID201 (lines 12–20). When SUFFIX is used, the child's value is the same as the parent's with "-CH" added to the end: ID201 and ID201-CH (lines 22–31).

Summary

Threads can communicate and signal each other by several mechanisms in Java. The wait/notify mechanism (wait(), notify(), and notifyAll()) provides a multithread-safe way for one thread to signal another that a value has changed. I showed you that it is important to make sure to avoid missed notifications and early notifications.

In addition, threads can use the join() method of Thread to wait for other threads to die. A thread can also stream data through a pipe to another thread using the classes PipedInputStream, PipedOutputStream, PipedReader, and PipedWriter.

Threads can also use thread-specific variables that keep a different value for different threads by using the classes ThreadLocal and InheritableThreadLocal.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.116.69