Improving the Accuracy of SecondCounter

Chapter 5 - Gracefully Stopping Threads

Java Thread Programming
Paul Hyde
  Copyright 1999 Sams Publishing

Suspending and Resuming Thread Execution
Sometimes you might want to temporarily pause or suspend an executing thread and at a later time let it resume execution. As an example, consider a program that uses a thread to animate some images by flipping through them sequentially. When the animated window is not visible (minimized or covered by another window), there isnt any necessity to continue doing the work of animation until the window is visible again. In fact, continuing to animate the images when no one can see them is wasting valuable processor cycles. Im sure that you will come across many other situations in which a thread should temporarily stop its work.
Using the Deprecated Methods suspend() and resume()
The Thread API contains two deprecated methods that are used to temporarily stop and later restart a thread:
public final void suspend()
public final void resume()
Although these methods are deprecated and shouldnt be used in new code, Ill show you an example of their use in case you inherit some older code that you have to maintain. The code for the class VisualSuspendResume is shown in Listing 5.6.
  Tip Methods and classes are deprecated by Sun Microsystems to indicate that developers should avoid using them. A deprecated method can still be used, but when the code is compiled, a warning is issued. Deprecation is used to indicate a method or class is obsolete or dangerous and may be removed from future releases of the JDK. Although you may still use deprecated methods, you should use them sparingly and only when absolutely necessary.
Listing 5.6  VisualSuspendResume.javaSuspending and Resuming Animation Using the Deprecated Methods
1: import java.awt.*;
2: import java.awt.event.*;
3: import javax.swing.*;
4:
5: public class VisualSuspendResume
6:             extends JPanel
7:             implements Runnable {
8:
9:     private static final String[] symbolList =
10:             { , /, -, \\, , /, -, \\ };
11:
12:     private Thread runThread;
13:     private JTextField symbolTF;
14:
15:     public VisualSuspendResume() {
16:         symbolTF = new JTextField();
17:         symbolTF.setEditable(false);
18:         symbolTF.setFont(new Font(Monospaced, Font.BOLD, 26));
19:         symbolTF.setHorizontalAlignment(JTextField.CENTER);
20:
21:         final JButton suspendB = new JButton(Suspend);
22:         final JButton resumeB = new JButton(Resume);
23:
24:         suspendB.addActionListener(new ActionListener() {
25:                 public void actionPerformed(ActionEvent e) {
26:                     suspendNow();
27:                 }
28:             });
29:
30:         resumeB.addActionListener(new ActionListener() {
31:                 public void actionPerformed(ActionEvent e) {
32:                     resumeNow();
33:                 }
34:             });
35:
36:         JPanel innerStackP = new JPanel();
37:         innerStackP.setLayout(new GridLayout(0, 1, 3, 3));
38:         innerStackP.add(symbolTF);
39:         innerStackP.add(suspendB);
40:         innerStackP.add(resumeB);
41:
42:         this.setLayout(new FlowLayout(FlowLayout.CENTER));
43:         this.add(innerStackP);
44:     }
45:
46:     private void suspendNow() {
47:         if (runThread != null) { // avoid NullPointerException
48:             runThread.suspend();
49:         }
50:     }
51:
52:     private void resumeNow() {
53:         if (runThread != null) { // avoid NullPointerException
54:             runThread.resume();
55:         }
56:     }
57:
58:     public void run() {
59:         try {
60:             // Store this for the suspendNow() and
61:             // resumeNow() methods to use.
62:             runThread = Thread.currentThread();
63:             int count = 0;
64:
65:             while (true) {
66:                 // each time through, show the next symbol
67:                 symbolTF.setText(
68:                     symbolList[ count % symbolList.length ]);
69:                 Thread.sleep(200);
70:                 count++;
71:             }
72:         } catch (InterruptedException x) {
73:             // ignore
74:         } finally {
75:             // The thread is about to die, make sure that the
76:             // reference to it is also lost.
77:             runThread = null;
78:         }
79:     }
80:
81:     public static void main(String[] args) {
82:         VisualSuspendResume vsr = new VisualSuspendResume();
83:         Thread t = new Thread(vsr);
84:         t.start();
85:
86:         JFrame f = new JFrame(Visual Suspend Resume);
87:         f.setContentPane(vsr);
88:         f.setSize(320, 200);
89:         f.setVisible(true);
90:         f.addWindowListener(new WindowAdapter() {
91:                 public void windowClosing(WindowEvent e) {
92:                     System.exit(0);
93:                 }
94:             });
95:     }
96: }
The VisualSuspendResume class subclasses JPanel and allows a thread to be run within it by implementing the Runnable interface (lines 57). A noneditable JTextField is created (lines 1619) and used to sequentially display each character defined in the set of symbols (lines 910). The symbols are displayed in a continuous loop to create the illusion of a line spinning about its center in a clockwise rotation. A Suspend button is added that calls suspendNow() when clicked (lines 21 and 2428). A Resume button is also added that calls resumeNow() when clicked (lines 22 and 3034). These three components are stacked vertically in the panel (lines 3643).
When a new thread enters run() , a reference to it is stored in runThread (line 62). Regardless of how the thread leaves the try block (lines 5971), it will enter the finally block and set the runThread reference back to null (lines 7478). This reference is used by suspendNow() and resumeNow() to control the thread.
As long as the thread is not suspended , it continues to execute the statements in the infinite while loop (lines 6571). Each time through the loop, the next symbol is retrieved from the list, and the JTextField is updated through the setText() method (lines 6768). After the thread sleeps for a bit (line 69), count is incremented (line 70) in preparation for retrieving the next symbol in the cycle.
When the Suspend button is clicked and the suspendNow() method is called, the thread pointed to by runThread is suspended (line 48). If runThread is not currently set, the request is simply ignored (line 47). Similarly, when the Resume button is clicked, resumeNow() is invoked and executes the resume() method of the Thread object (line 54) for the thread currently inside run() .
Figure 5.1 shows a snapshot of how VisualSuspendResume looks when running.
Figure 5.1: A screen shot of VisualSuspendResume while running.
When you run this code, notice how the spinner stops when Suspend is clicked and how it starts again when Resume is clicked. Also note that clicking Suspend several times in a row has no adverse effects. It does no harm to suspend a thread that is already suspended. Likewise, clicking Resume several times in a row is also fine. It does no harm to resume a thread that is currently running.
The suspend() method is deprecated as of JDK 1.2 because if a thread is suspended at an inopportune timesuch as when it is holding a lock for a shared resourcea deadlock condition may result. I explain and demonstrate deadlocks in Chapter 7, Concurrent Access to Objects and Variables, but let it suffice to say for now that a deadlock is a very bad thing and can cause a program to freeze up on a user . Even when locks are not involved, a thread might be suspended while in the middle of a long procedure that really should not be left in a partially completed state. The resume() method is deprecated because without any use of suspend() , it is not needed.
  Tip Although suspend() and resume() are not deprecated in JDK 1.0 and 1.1, they should not be used for the same good reasons that they are deprecated in JDK 1.2. Alternative techniques to very closely simulate and safely replace the functionality of suspend() and resume() are presented next.
Suspending at a Bad Time
Although suspending a thread while it is holding a lock on a shared resource can be disastrous, suspending it while its in the middle of long-running computation can also lead to problems. The sample class DeprecatedSuspendResume , shown in Listing 5.7, slows things down with some sleeps to make a thread more likely to be suspended at an inopportune time.
Listing 5.7  DeprecatedSuspendResume.javaAn Example of Suspension at a Bad Time
1: public class DeprecatedSuspendResume
2:         extends Object
3:         implements Runnable {
4:
5:     private volatile int firstVal;
6:     private volatile int secondVal;
7:
8:     public boolean areValuesEqual() {
9:         return (firstVal == secondVal);
10:     }
11:
12:     public void run() {
13:         try {
14:             firstVal = 0;
15:             secondVal = 0;
16:             workMethod();
17:         } catch (InterruptedException x) {
18:             System.out.println(
19:                     interrupted while in workMethod());
20:         }
21:     }
22:
23:     private void workMethod() throws InterruptedException {
24:         int val = 1;
25:
26:         while (true) {
27:             stepOne(val);
28:             stepTwo(val);
29:             val++;
30:
31:             Thread.sleep(200);  // pause before looping again
32:         }
33:     }
34:
35:     private void stepOne(int newVal)
36:             throws InterruptedException {
37:
38:         firstVal = newVal;
39:         Thread.sleep(300);  // simulate some other long process
40:     }
41:
42:     private void stepTwo(int newVal) {
43:         secondVal = newVal;
44:     }
45:
46:     public static void main(String[] args) {
47:         DeprecatedSuspendResume dsr =
48:                 new DeprecatedSuspendResume();
49:         Thread t = new Thread(dsr);
50:         t.start();
51:
52:         // let the other thread get going and run for a while
53:         try { Thread.sleep(1000); }
54:         catch (InterruptedException x) { }
55:
56:         for (int i = 0; i < 10; i++) {
57:             t.suspend();
58:             System.out.println(dsr.areValuesEqual()= +
59:                                 dsr.areValuesEqual());
60:             t.resume();
61:             try {
62:                 // Pause for a random amount of time
63:                 // between 0 and 2 seconds.
64:                 Thread.sleep(
65:                         (long) (Math.random() * 2000.0));
66:             } catch (InterruptedException x) {
67:                 // ignore
68:             }
69:         }
70:
71:         System.exit(0); // abruptly terminate application
72:     }
73: }
In main() , a new DeprecatedSuspendResume object is instantiated and a new thread is spawned to run it (lines 4750). The main thread pauses for a second to let the new thread get up and running (line 53).
The newly spawned thread begins execution in run() (line 12) and then enters a try / catch block that catches any InterruptedException that might be thrown (lines 1320). After making sure that both values are initially set to zero (lines 1415), run() invokes workMethod() . In workMethod() , val is initially set to 1 , incremented each time through the while loop, and passed as a parameter to both stepOne() and stepTwo() . In stepOne() (lines 3540), firstVal is assigned its new value (line 38), and then a sleep() is used to simulate some other task that takes some time to execute (line 39). In stepTwo() (lines 4244), secondVal is assigned its new value, and then it immediately returns (line 43). Back in workMethod() , a sleep() is used to slow things down before looping again (line 31).
Trouble arises when the new thread is suspended after it has set firstVal , but before it has set secondVal . Meanwhile, the main thread enters the for loop (lines 5669). Each time through, the new thread is suspended (line 57) to check whether firstVal and secondVal are equal (lines 5859). After the check, the thread is allowed to resume execution (line 60). The main thread then sleeps for a random amount of time between 0.0 and 2.0 seconds (lines 6168) before jumping back up to the for . After 10 iterations, System.exit() is used to terminate all threads and exit the VM (line 71).
When this DeprecatedSuspendResume is run, output something like the following sample should be produced (your output will differ somewhat):
dsr.areValuesEqual()=true
dsr.areValuesEqual()=false
dsr.areValuesEqual()=false
dsr.areValuesEqual()=false
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=false
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=false
Notice that the value returned from areValuesEqual() is sometimes true and sometimes false . It is false when the new thread is suspended some time after stepOne() is called and before stepTwo() is completed. This is a bad time for the suspension to occur, but it cannot be avoided because the thread has no control over when its suspend method is called.
Suspending and Resuming Without Deprecated Methods
The method suspend() may be invoked on a particular thread at any time. In most programs, there are typically times when a thread is holding a lock or is in the middle of a long-running calculation and should not be suspended. In fact, suspending a thread while it is holding a lock can easily lead to a deadlock condition. Also, the suspend() and resume() methods have been wisely deprecated in the most recent JDK. In spite of all these reasons not to use suspend() , there are still situations where a threads activities should be temporarily suspended. The AlternateSuspendResume class shown in Listing 5.8 demonstrates another way to achieve this.
Listing 5.8  AlternateSuspendResume.javaAvoiding the Use of suspend() and resume()
  1: public class AlternateSuspendResume
  2:         extends Object
  3:         implements Runnable {
  4:
  5:     private volatile int firstVal;
  6:     private volatile int secondVal;
  7:     private volatile boolean suspended;
  8:
  9:     public boolean areValuesEqual() {
10:         return (firstVal == secondVal);
11:     }
12:
13:     public void run() {
14:         try {
15:             suspended = false;
16:             firstVal = 0;
17:             secondVal = 0;
18:             workMethod();
19:         } catch (InterruptedException x) {
20:             System.out.println(
21:                 interrupted while in workMethod());
22:         }
23:     }
24:
25:     private void workMethod() throws InterruptedException {
26:         int val = 1;
27:
28:         while (true) {
29:             // blocks only if suspended is true
30:             waitWhileSuspended();
31:
32:             stepOne(val);
33:             stepTwo(val);
34:             val++;
35:
36:             // blocks only if suspended is true
37:             waitWhileSuspended();
38:
39:             Thread.sleep(200);  // pause before looping again
40:         }
41:     }
42:
43:     private void stepOne(int newVal)
44:                     throws InterruptedException {
45:
46:         firstVal = newVal;
47:
48:         // simulate some other lengthy process
49:         Thread.sleep(300); 
50:     }
51:
52:     private void stepTwo(int newVal) {
53:         secondVal = newVal;
54:     }
55:
56:     public void suspendRequest() {
57:         suspended = true;
58:     }
59:
60:     public void resumeRequest()
61:         suspended = false;
62:     }
63:
64:     private void waitWhileSuspended()
65:                 throws InterruptedException {
66:
67:         // This is an example of a busy wait technique.  It is
68:         // not the best way to wait for a condition to change
69:         // because it continually requires some processor
70:         // cycles to perform the checks.  A better technique
71:         // is to use Javas built-in wait-notify mechanism.
72:         while ( suspended ) {
73:             Thread. sleep (200);
74:         }
75:     }
76:
77:     public static void main(String[] args) {
78:         AlternateSuspendResume asr =
79:                 new AlternateSuspendResume();
80:
81:         Thread t = new Thread(asr);
82:         t.start();
83:
84:         // let the other thread get going and run for a while
85:         try { Thread.sleep(1000); }
86:         catch (InterruptedException x) { }
87:
88:         for (int i = 0; i < 10; i++) {
89:             asr.suspendRequest();
90:
91:             // Give the thread a chance to notice the
92:             // suspension request.
93:             try { Thread.sleep(350); }
94:             catch (InterruptedException x) { }
95:
96:             System.out.println(dsr.areValuesEqual()= +
97:                     asr.areValuesEqual());
98:
99:            asr.resumeRequest();
100:
101:             try {
102:                 // Pause for a random amount of time
103:                 // between 0 and 2 seconds.
104:                 Thread.sleep(
105:                         (long) (Math.random() * 2000.0));
106:             } catch (InterruptedException x) {
107:                 // ignore
108:             }
109:         }
110:
111:         System.exit(0); // abruptly terminate application
112:     }
113: }
A new volatile member variable suspended is added (line 7) to keep track of a request to have the internal thread temporarily stop processing. This flag is initially false (line 15) in the beginning of run() . It is set to true every time the suspendRequest() method (lines 5658) is invoked. It is set back to false every time resumeRequest() (lines 6062) is invoked. The current value of suspended is checked in waitWhileSuspended() (lines 6475), and if true , it will check (line 72) five times per second (every 200 milliseconds ) to see whether the value has changed. Calls to waitWhileSuspended() do not return until suspended is false . If suspended was already false , calls return right away.
The loop inside waitWhileSuspended() is performing a busy wait . That is, the thread sleeps briefly between checks, but uses up processor cycles repeatedly performing these checks. If the sleep interval is reduced, even more processor resources are wasted . If the sleep interval is increased, it will be longer (on average) before it notices a change in the monitored value. (Later in this chapter, the class BestReplacement uses the wait/notify mechanism instead of a busy wait to provide the same functionality more efficiently .)
  Tip Busy waits are wasteful of processor resources and should be avoided. Using Javas wait/notify mechanism is a much better design decision in most cases. Ill show you how to use this mechanism in Chapter 8 .
You can see that waitWhileSuspended() is invoked twice within the while loop of workMethod() (lines 30 and 37). Both these times, it would be all right for the thread to be suspended for a while. If it has not been suspended, these calls return right away.
  Tip If there is more than one safe place to suspend a thread in your code, you should add more waitWhileSuspended() method calls to all the safe places. Just make sure that you dont allow suspensions while holding a lock! Calls to waitWhileSuspended() should be done frequently to help the thread respond quickly to a suspend request. At the same time, keep in mind that invoking waitWhileSuspended() uses some processor resources, so dont use it too frequently. Ill provide you with a detailed explanation of locks in Chapter 7, Concurrent Access to Objects and Variables.
In the main() method, instead of invoking suspend() on the Thread object, the suspendRequest() method is invoked on the AlternateSuspendResume object (line 89). As the name implies, this is only a request that the internal thread temporarily pause. It will continue to run until the next time that it calls waitWhileSuspended() , allowing any partially completed tasks to finish. After putting in the request, the main thread sleeps for 350 milliseconds (line 93) to allow the new thread enough time to get to its next waitWhileSuspended() call. The areValuesEqual() results are printed (lines 9697), and then the resumeRequest() method is called (line 99) to signal the new thread that it can now return from waitWhileSuspended() .
When AlternateSuspendResume is run, output looks like this (your output should match):
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
dsr.areValuesEqual()=true
The only way that it would not find values equal would be if suspendRequest() had been called by the main thread, and the internal thread had not yet called waitWhileSuspended() . The 350-millisecond sleep used before checking the values should be adequate in this case. If you need to be sure that the internal thread has entered waitWhileSuspended() before taking some sort of action, you could introduce another boolean member variable that is true only while the internal thread is inside waitWhileSuspended() . In the BestReplacement class at the end of this chapter, I will show you a way that this can be done.
This new way of suspending and resuming thread execution via requests is far safer than having a thread directly and abruptly suspended via the suspend() method of Thread .

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