Speeding Concurrent Access

Chapter 8 - Inter-thread Communication

Java Thread Programming
Paul Hyde
  Copyright 1999 Sams Publishing

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 dont 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.
Listing 8.1  MissedNotify.javaAn 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 919) 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 2131) 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 3876), 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 6972), 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.
Listing 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 25). 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 814).
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.
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 .
Listing 8.3  MissedNotifyFix.javaA 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 3241) of proceed() (lines 2944), 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 1424) of waitToProceed() (lines 1127), okToProceed is checked to see if any waiting is necessary. Instead of just using an if statement to check, a while statement (lines 1721) 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 27) before threadA enters waitToProceed() (line 8). With this indicator fix in place, threadA moves right through waitToProceed() (lines 811). Notice that none of the messages about wait() are printed. The indicator variable kept threadA from waiting for notification that would never come.
Listing 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() .
Figure 8.3: Timeline of events for MissedNotifyFix.

Toc


Java Thread Programming
Java Thread Programming
ISBN: 0672315858
EAN: 2147483647
Year: 2005
Pages: 149
Authors: Paul Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net