Using Thread.sleep()

Chapter 4 - Implementing Runnable Versus Extending Thread

Java Thread Programming
Paul Hyde
  Copyright 1999 Sams Publishing

Visual Timer Graphical Component
Imagine that what you need is a timer graphical component that continually displays the time elapsed since it was started. To build this custom component, at a bare minimum, you must extend Component . Because this example uses Swing , you will instead extend JComponent (which indirectly extends Component ). Figure 4.1 shows the initial class hierarchy for the new customized component, SecondCounter .
Figure 4.1: The initial class hierarchy for SecondCounter.
SecondCounter IS-A Component , so it can be added to any Container , just like the other Component s. This SecondCounter has to keep track of the amount of time that has passed since it was started and update itself every 0.1 seconds to visually reflect the time that has elapsed.
Listing 4.1 shows the source code for a first cut at defining this class. This version definitely has serious problems, but it illustrates the necessity of another approach. To keep the evolving versions of SecondCounter straight, slightly different classnames are used for each version.  In this case, the class name is SecondCounterLockup .
Listing 4.1  SecondCounterLockup.javaThe First Attempt at the Timer
1: import java.awt.*;
2: import javax.swing.*;
3: import java.text.*;
4:
5: public class SecondCounterLockup extends JComponent {
6:     private boolean keepRunning;
7:     private Font paintFont;
8:     private String timeMsg;
9:     private int arcLen;
10:
11:     public SecondCounterLockup() {
12:         paintFont = new Font(SansSerif, Font.BOLD, 14);
13:         timeMsg = never started;
14:         arcLen = 0;
15:     }
16:
17:     public void runClock() {
18:         System.out.println(thread running runClock() is +
19:                 Thread.currentThread().getName());
20:
21:         DecimalFormat fmt = new DecimalFormat(0.000);
22:         long normalSleepTime = 100;
23:
24:         int counter = 0;
25:         keepRunning = true;
26:
27:         while (keepRunning) {
28:             try {
29:                 Thread.sleep(normalSleepTime);
30:             } catch (InterruptedException x) {
31:                 // ignore
32:             }
33:
34:             counter++;
35:             double counterSecs = counter / 10.0;
36:
37:             timeMsg = fmt.format(counterSecs);
38:
39:             arcLen = (((int) counterSecs) % 60) * 360 / 60;
40:             repaint();
41:         }
42:     }
43:
44:     public void stopClock() {
45:         keepRunning = false;
46:     }
47:
48:     public void paint(Graphics g) {
49:         System.out.println(thread that invoked paint() is +
50:                 Thread.currentThread().getName());
51:
52:         g.setColor( Color .black);
53:         g.setFont(paintFont);
54:         g.drawString(timeMsg, 0, 15);
55:
56:         g.fillOval(0, 20, 100, 100);  // black border
57:
58:         g.setColor(Color.white);
59:         g.fillOval(3, 23, 94, 94);  // white for unused portion
60:
61:         g.setColor(Color.blue);  // blue for used portion
62:         g.fillArc(2, 22, 96, 96, 90, -arcLen);
63:     }
64: }
This component draws itself with a text message and a circular dial. Initially, the text message is never started (line 13), and the dial is totally white ( arcLen = 0 , line 14). After the timer is started, the text message indicates the total elapsed time in fractional seconds. The dial sweeps out a blue-filled arc in a clockwise direction that completes 360 degrees every 60 seconds. The dial portion is very similar to the second hand on an analog watch or clock.
The paint() method (lines 4863) handles the drawing of the component based on the current values of timeMsg and arcLen . In addition, on lines 49 and 50, paint() reveals the name of the thread that invoked it.
The runClock() method is called when the timer should begin counting (line 17), and it also shares the name of the thread that invoked it (lines 18 and 19). On line 21, the format for the textual display of the seconds elapsed is defined to show fractional seconds down to the millisecond (ms). The normalSleepTime is defined as 100ms, which is the 0.1-second interval between updates that was desired. The number of iterations is held in counter (line 24). Initially, keepRunning is set to true to indicate that the timer should continue to run (line 25). The remainder of the method is a while loop (lines 2741). In this loop, a quick nap is taken for 1/10 second (lines 2832). Then, counter is incremented to indicate that another 0.1 seconds has passed (line 34). This count is converted to seconds: counterSecs (line 35). On line 37, the number of seconds is formatted into a String for display in the paint() method. The arc length in degrees is calculated for use in the paint() method (line 39). Finally, repaint() is called to let the JavaVM know that it should schedule a call to paint() as soon as it can.
The method stopClock() (lines 4446) is invoked to signal that the timer should stop running. It sets keepRunning to false so that the next time the while expression in runClock() is evaluated, it will stop looping.
To use this customized component in a JFrame with other components , another class is defined: SecondCounterLockupMain , shown in Listing 4.2.
Listing 4.2  SecondCounterLockupMain.javaThe Class Used to Demonstrate SecondCounterLockup
1: import java.awt.*;
2: import java.awt.event.*;
3: import javax.swing.*;
4: import javax.swing.border.*;
5:
6: public class SecondCounterLockupMain extends JPanel {
7:     private SecondCounterLockup sc;
8:     private JButton startB;
9:     private JButton stopB;
10:
11:     public SecondCounterLockupMain() {
12:         sc = new SecondCounterLockup();
13:         startB = new JButton(Start);
14:         stopB = new JButton(Stop);
15:
16:         stopB.setEnabled(false);  // begin with this disabled
17:
18:         startB.addActionListener(new ActionListener() {
19:                 public void actionPerformed(ActionEvent e) {
20:                     // disable to stop more start requests
21:                     startB.setEnabled(false);
22:
23:                     // Run the counter. Watch out, trouble here!
24:                     sc.runClock();
25:
26:                     stopB.setEnabled(true);
27:                     stopB.requestFocus();
28:                 }
29:             });
30:
31:         stopB.addActionListener(new ActionListener() {
32:                 public void actionPerformed(ActionEvent e) {
33:                     stopB.setEnabled(false);
34:                     sc.stopClock();
35:                     startB.setEnabled(true);
36:                     startB.requestFocus();
37:                 }
38:             });
39:
40:         JPanel innerButtonP = new JPanel();
41:         innerButtonP.setLayout(new GridLayout(0, 1, 0, 3));
42:         innerButtonP.add(startB);
43:         innerButtonP.add(stopB);
44:
45:         JPanel buttonP = new JPanel();
46:         buttonP.setLayout(new BorderLayout());
47:         buttonP.add(innerButtonP, BorderLayout.NORTH);
48:
49:         this.setLayout(new BorderLayout(10, 10));
50:         this.setBorder(new EmptyBorder(20, 20, 20, 20));
51:         this.add(buttonP, BorderLayout.WEST);
52:         this.add(sc, BorderLayout.CENTER);
53:     }
54:
55:     public static void main(String[] args) {
56:         SecondCounterLockupMain scm = new SecondCounterLockupMain();
57:
58:         JFrame f = new JFrame(Second Counter Lockup);
59:         f.setContentPane(scm);
60:         f.setSize(320, 200);
61:         f.setVisible(true);
62:         f.addWindowListener(new WindowAdapter() {
63:                 public void windowClosing(WindowEvent e) {
64:                     System.exit(0);
65:                 }
66:             });
67:     }
68: }
In the constructor, a new SecondCounterLockup is created and put into a JPanel with Start and Stop buttons (lines 1214 and 4052). In main() , this JPanel 5567). Initially, the Stop button is disabled because the timer is not running (line 16).
When the Start button is pressed, the actionPerformed() method is invoked (lines 1928) on the anonymous inner subclass of ActionListener (line 18). In there, the Start button is first disabled (line 21) to prevent any further pressing until the timer is stopped . Next, the runClock() method on the SecondCounterLockup object is invoked (line 24). Watch out, heres where the trouble starts! In fact, in this example, none of the other code is ever executed.
  Caution As the name suggests, running SecondCounterLockupMain will not work as intended and locks up the JavaVM. This is harmless and should not stop you from trying the example. When the Start button is pressed, nothing else happens in the application. The Stop button is never enabled. The window exit/close control is ineffective , even though code was written (lines 6266) to handle this event.
  The only way to stop the application is to kill the JavaVM (by going back to the console window and pressing Ctrl+C, or Delete, or whatever mechanism is used on your platform to kill/interrupt/break/terminate a runaway process).
Figure 4.2 shows how this application looks right after startup:
java SecondCounterLockupMain
Figure 4.2: Just after starting SecondCounterLockupMain.
Figure 4.3 shows how it looks after the Start button is pressed (and how it looks until it is killed !). Although the clock variables are being updated internally, the external view never has a chance to be painted . The paint() method is called only one time, when the frame is first drawn, and never displays the changes requested by the repaint() call.
Figure 4.3: After clicking the Start button.
A clue to the problem can be found in the output on the console:
thread that invoked paint() is AWT-EventQueue-0
thread running runClock() is AWT-EventQueue-0
This shows that the AWT-EventQueue-0 thread is used for both painting and invoking the event handling methods . When the Start button is pressed, the AWT-EventQueue-0 thread invokes the actionPerformed() method (line 19, SecondCounterLockupMain.java). This method in turn invokes runClock() , which continues to loop until keepRunning is set to false . The only way this can be set to false is by the pressing the Stop button. Because the AWT-EventQueue-0 thread is busy in this loop, it never has a chance to invoke the paint() method again, and the display is frozen. No other event can be processed (including the window close event) until the actionPerformed() method returns. But this will never happen! The application is all locked up, spinning in the while loop!
Although this is only an example, this is a very real type of problem. Rather than do any major work in the event handling thread, you should use another thread as the worker and allow the event handling thread to return to the business of handling events.
  Tip GUI event handling code should be relatively brief to allow the event handling thread to return from the handler and prepare to handle the next event. If longer tasks must be performed, the bulk of the work should be passed off to another thread for processing. This helps keep the user interface lively and responsive .

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