Chapter 5: Gracefully Stopping Threads

Chapter 5 - Gracefully Stopping Threads

Java Thread Programming
Paul Hyde
  Copyright 1999 Sams Publishing

The Best Replacement for stop(), suspend(), and resume()
The class BestReplacement presented in Listing 5.11 combines some techniques from other chapters to create a model class that effectively eliminates the need for the three deprecated methods stop() , suspend() , and resume() .
Listing 5.11  BestReplacement.javaCombined Alternative Techniques
  1: // uses BooleanLock from chapter 17
  2:
  3: public class BestReplacement extends Object {
  4:     private Thread internalThread;
  5:     private volatile boolean stopRequested;
  6:
  7:     private BooleanLock suspendRequested;
  8:     private BooleanLock internalThreadSuspended;
  9:
10:     public BestReplacement() {
11:         stopRequested = false;
12:
13:         suspendRequested = new BooleanLock(false);
14:         internalThreadSuspended = new BooleanLock(false);
15:
16:         Runnable r = new Runnable() {
17:                 public void run() {
18:                     try {
19:                         runWork();
20:                     } catch (Exception x) {
21:                         // in case ANY exception slips through
22:                         x.printStackTrace();
23:                     }
24:                 }
25:             };
26:
27:         internalThread = new Thread(r);
28:         internalThread.start();
29:     }
30:
31:     private void runWork() {
32:         int count = 0;
33:
34:         while (!stopRequested) {
35:             try {
36:                 waitWhileSuspended();
37:             } catch (InterruptedException x) {
38:                 // Reassert interrupt so that remaining code
39:                 // sees that an interrupt has been requested .
40:                 Thread.currentThread().interrupt();
41:
42:                 // Reevaluate while condition --probably
43:                 // false now.
44:                 continue;
45:             }
46:
47:             System.out.println(Part I - count= + count);
48:
49:             try {
50:                 Thread.sleep(1000);
51:             } catch (InterruptedException x) {
52:                 Thread.currentThread().interrupt(); // reassert
53:                 // continue on as if sleep completed normally
54:             }
55:
56:             System.out.println(Part II - count= + count);
57:
58:             try {
59:                 Thread.sleep(1000);
60:             } catch (InterruptedException x) {
61:                 Thread.currentThread().interrupt(); // reassert
62:                 // continue on as if sleep completed normally
63:             }
64:
65:             System.out.println(Part III - count= + count);
66:
67:             count++;
68:         }
69:     }
70:
71:     private void waitWhileSuspended()
72:                     throws InterruptedException {
73:
74:         // only called by the internal thread - private method
75:
76:         synchronized (suspendRequested) {
77:             if (suspendRequested.isTrue()) {
78:                 try {
79:                     internalThreadSuspended.setValue(true);
80:                     suspendRequested.waitUntilFalse(0);
81:                 } finally {
82:                     internalThreadSuspended.setValue(false);
83:                 }
84:             }
85:         }
86:     }
87:
88:     public void suspendRequest() {
89:         suspendRequested.setValue(true);
90:     }
91:
92:     public void resumeRequest() {
93:         suspendRequested.setValue(false);
94:     }
95:
96:     public boolean waitForActualSuspension(long msTimeout)
97:                     throws InterruptedException {
98:
99:         // Returns ˜true if suspended , ˜false if the
100:         // timeout expired .
104:
105:     public void stopRequest() {
106:         stopRequested = true;
107:         internalThread.interrupt();
108:     }
109:
110:     public boolean isAlive() {
111:         return internalThread.isAlive();
112:     }
113:
114:     public static void main(String[] args) {
115:         try {
116:             BestReplacement br = new BestReplacement();
117:             System.out.println(
118:                     --> just created, br.isAlive()= +
119:                     br.isAlive());
120:             Thread.sleep(4200);
121:
122:             long startTime = System.currentTimeMillis();
123:             br.suspendRequest();
124:             System.out.println(
125:                     --> just submitted a suspendRequest);
126:
127:             boolean suspensionTookEffect =
128:                     br.waitForActualSuspension(10000);
129:             long stopTime = System.currentTimeMillis();
130:
131:             if (suspensionTookEffect) {
132:                 System.out.println(
133:                     --> the internal thread took +
134:                     (stopTime - startTime) + ms to notice +
135:                     \n    the suspend request and is now +
136:                     suspended.);
137:             } else {
138:                 System.out.println(
139:                     --> the internal thread did not notice +
140:                     the suspend request +
141:                     \n    within 10 seconds.);
142:             }
143:
144:             Thread.sleep(5000);
145:
146:             br.resumeRequest();
147:             System.out.println(
148:                     --> just submitted a resumeRequest);
149:             Thread.sleep(2200);
150:
151:             br.stopRequest();
152:             System.out.println(
153:                     --> just submitted a stopRequest);
154:         } catch (InterruptedException x) {
155:             // ignore
156:         }
157:     }
158: }
In writing BestReplacement , I used some techniques from other chapters. It is a self-running object (for more details, see the technique in Chapter 11, Self-Running Objects ) in the sense that from outside the class, there is no indication that a thread will be running internally (although the classs documentation should mention this fact). A user of this class does not have to create a thread to run within it; one is created (line 27) and started (line 28) automatically in the constructor. The reference to this thread is held in a private member variable (line 4). The Runnable used by the internal thread is an inner class (lines 1625). By using an inner class, the public void run() method is hidden and cannot be erroneously called by external code. Within this inner class, the private void runWork() method is invoked by the internal thread (line 19). In your design, if the thread should not be started in the constructor, you can include another method to allow the internalThread.start() operation to be performed when appropriate.
Additionally, the BooleanLock class is used from Chapter 17 . BooleanLock encapsulates the wait/notify mechanism inside a class that holds a boolean value. The setValue() method is used to change the internal value and signal any and all threads waiting for the value to change. Other threads can wait for the value to be changed by invoking methods like waitUntilTrue() and waitUntilFalse() . In BestReplacement , two instances of BooleanLock are used. The suspendRequested instance (line 7) is used to track whether a suspend has been requested. The internalThreadSuspended instance (line 8) is used to determine if the internal thread has noticed a suspend request. Both are initially set to false (lines 1314).
Inside the runWork() method, the while loop (lines 3468) continues until stopRequested is true . Each time through, waitWhileSuspended() is called (line 36) to block, if currently suspended. This is a safe place for the internal thread to be suspended or stopped . If the internal thread is interrupted while waiting, it will throw an InterruptedException . The interrupt is used only to signal that the thread should die as soon as possible. This exception is caught, and the thread is reinterrupted (line 40) in case any other remaining statement becomes stuck and jumps back up to the top of the while because of the continue statement (line 44).
If not currently suspended, or after being resumed, the internal thread proceeds through the statements and prints various messages (lines 47, 56, and 65). Several sleeps are used and if interrupted, catch the exception and reassert the interrupted status (lines 52 and 61).
In suspendRequest() , the suspendRequested instance of BooleanLock has its value set to true (line 89). In resumeRequest() , suspendRequest is set to false (line 93). All of the synchronization and notification necessary for changing the value is encapsulated inside BooleanLock .
In waitWhileSuspended() (lines 7186), a busy wait is avoided by using a BooleanLock instance ( BooleanLock uses the wait/notify mechanism internally). First, the internal thread blocks until it can acquire exclusive access to the object-level lock on suspendRequested (line 76). If it is currently suspended, it enters the try / finally block (lines 7784); otherwise , it simply returns. In the try / catch block, the internal thread indicates that it has noticed the suspend request by setting the state of internalThreadSuspened to true (line 79). The internal thread then invokes the waitUntilFalse() method of suspendRequested with a timeout of to indicate that it should never timeout (line 80). No matter what happens, when the internal thread leaves the try section, it enters the finally section where the state of internalThreadSuspended is set back to false (line 82).
If an external thread wants to know if the internal thread has noticed the suspend request, the external thread can invoke waitForActualSuspension() (lines 96103). This blocks waiting (up to the timeout) until internalThreadSuspended is set to true .
  Tip Dont worry if you dont fully understand the use of synchronized and the wait/notify mechanism encapsulated in BooleanLock at this time. I fully explain them in Chapters 7 , 8 , and 17 .
The internal thread can be stopped by invoking stopRequest() . This method first sets the stopRequest boolean flag to true (line 106). It then interrupts the internal thread to unstick it from any blocking sleeps or waits (line 107). The isAlive() method is used to check whether the internal thread has died (line 111).
When run, output such as the following is produced:
--> just created, br.isAlive()=true
Part I - count=0
Part II - count=0
Part III - count=0
Part I - count=1
Part II - count=1
Part III - count=1
Part I - count=2
--> just submitted a suspendRequest
Part II - count=2
Part III - count=2
--> the internal thread took 1810 ms to notice
    the suspend request and is now suspended.
--> just submitted a resumeRequest
Part I - count=3
Part II - count=3
Part III - count=3
Part I - count=4
--> just submitted a stopRequest
Part II - count=4
Part III - count=4
Notice that when a suspend request is submitted, the loop continues until the suspend check at the top ( waitWhileSuspended() ). Also notice that when stopped, the internal thread does not immediately terminate, but instead finishes its tasks in an orderly manner.
  Caution Keep in mind that this stopping feature requires that a blocked statement respond to an interrupt. This is not always the case. For example, the read() method on an InputStream blocks until a new byte is available, the end of stream is reached, or an IOException is thrown. It does not throw an InterruptedException if the blocked thread is interrupted! A technique for dealing with this situation is offered in Chapter 15, Breaking Out of a Blocked I/O State.

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