Summary

Chapter 8 - Inter-thread Communication

Java Thread Programming
Paul Hyde
  Copyright 1999 Sams Publishing

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 its 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.
Listing 8.5  EarlyNotify.javaAn 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:             // Therell 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!!!\n + 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 69), a multithread-safe List is created and used to hold the items added and removed. When a thread enters the removeItem() method (lines 1026), 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 2842), 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 4994), 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.
Listing 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 1011). 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 1314). 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.
Listing 8.7  EarlyNotifyFix.javaManaging 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:             // Therell 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!!!\n + 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.
Listing 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 911). 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, Ill 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.

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