Visual Timer Graphical Component

Chapter 4 - Implementing Runnable Versus Extending Thread

Java Thread Programming
Paul Hyde
  Copyright 1999 Sams Publishing

Modifying SecondCounter to Use Runnable
Listing 4.3 shows a new version of the timer component that now implements the Runnable interface. The bold text is used to indicate the most notable changes.
Listing 4.3  SecondCounterRunnable.javaImplementing the Runnable Interface
1: import java.awt.*;
2: import javax.swing.*;
3: import java.text.*;
4:
5: public class SecondCounterRunnable extends JComponent implements Runnable {
6:     private volatile boolean keepRunning;
7:     private Font paintFont;
8:     private volatile String timeMsg;
9:     private volatile int arcLen;
10:
11:     public SecondCounterRunnable() {
12:         paintFont = new Font(SansSerif, Font.BOLD, 14);
13:         timeMsg = never started;
14:         arcLen = 0;
15:     }
16:
17:     public void run() {
18:         runClock();
19:     }
20:
21:     public void runClock() {
22:         DecimalFormat fmt = new DecimalFormat(0.000);
23:         long normalSleepTime = 100;
24:
25:         int counter = 0;
26:         keepRunning = true;
27:
28:         while (keepRunning) {
29:             try {
30:                 Thread.sleep(normalSleepTime);
31:             } catch (InterruptedException x) {
32:                 // ignore
33:             }
34:
35:             counter++;
36:             double counterSecs = counter / 10.0;
37:
38:             timeMsg = fmt.format(counterSecs);
39:
40:             arcLen = (((int) counterSecs) % 60) * 360 / 60;
41:             repaint();
42:         }
43:     }
44:
45:     public void stopClock() {
46:         keepRunning = false;
47:     }
48:
49:     public void paint(Graphics g) {
50:         g.setColor( Color .black);
51:         g.setFont(paintFont);
52:         g.drawString(timeMsg, 0, 15);
53:
54:         g.fillOval(0, 20, 100, 100);  // black border
55:
56:         g.setColor(Color.white);
57:         g.fillOval(3, 23, 94, 94);  // white for unused portion
58:
59:         g.setColor(Color.blue);  // blue for used portion
60:         g.fillArc(2, 22, 96, 96, 90, -arcLen);
61:     }
62: }
Line 5 shows how the new class SecondCounterRunnable is simultaneously both a JComponent and a Runnable :
5: public class SecondCounterRunnable extends JComponent implements Runnable {
This allows it to be added to GUI containers and to have a thread of its own running within it. The requirements of the Runnable interface are met by the run() method (lines 1719). When the new thread enters run() , it simply invokes the runClock() method where the work of continually updating the component is done. This new thread continues to loop (lines 2842) every 0.1 seconds until the member variable keepRunning is set to false . This member variable is set to false when another thread (probably the event handling thread) invokes the stopClock() method (lines 4547).
  Note On lines 6, 8, and 9 of Listing 4.3 , the modifier volatile is included for some of the member variables . By indicating that a member variable is volatile , you inform the JavaVM that its value might be changed by one thread while being used by another. In this case, one thread is checking keepRunning , and another thread will change its value to false some time after the timer is started. Under certain circumstances, if the variable was not marked as volatile , the while loop would not see the new value and would run the timer forever. This is an important detail often overlooked (even by experienced developers) and is discussed in detail in Chapter 7, Concurrent Access to Objects and Variables.
Listing 4.4 shows the code for SecondCounterRunnableMain to work with SecondCounterRunnable .
Listing 4.4  SecondCounterRunnableMain.javaSupporting Code to Use SecondCounterRunnable
1: import java.awt.*;
2: import java.awt.event.*;
3: import javax.swing.*;
4: import javax.swing.border.*;
5:
6: public class SecondCounterRunnableMain extends JPanel {
7:     private SecondCounterRunnable sc;
8:     private JButton startB;
9:     private JButton stopB;
10:
11:     public SecondCounterRunnableMain() {
12:         sc = new SecondCounterRunnable();
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:                     // thread to run the counter
24:                     Thread counterThread = new Thread(sc, SecondCounter);
25:                     counterThread.start();
26:
27:                     stopB.setEnabled(true);
28:                     stopB.requestFocus();
29:                 }
30:             });
31:
32:         stopB.addActionListener(new ActionListener() {
33:                 public void actionPerformed(ActionEvent e) {
34:                     stopB.setEnabled(false);
35:                     sc.stopClock();
36:                     startB.setEnabled(true);
37:                     startB.requestFocus();
38:                 }
39:             });
40:
41:         JPanel innerButtonP = new JPanel();
42:         innerButtonP.setLayout(new GridLayout(0, 1, 0, 3));
43:         innerButtonP.add(startB);
44:         innerButtonP.add(stopB);
45:
46:         JPanel buttonP = new JPanel();
47:         buttonP.setLayout(new BorderLayout());
48:         buttonP.add(innerButtonP, BorderLayout.NORTH);
49:
50:         this.setLayout(new BorderLayout(10, 10));
51:         this.setBorder(new EmptyBorder(20, 20, 20, 20));
52:         this.add(buttonP, BorderLayout.WEST);
53:         this.add(sc, BorderLayout.CENTER);
54:     }
55:
56:     public static void main(String[] args) {
57:         SecondCounterRunnableMain scm = new SecondCounterRunnableMain();
58:
59:         JFrame f = new JFrame(Second Counter Runnable);
60:         f.setContentPane(scm);
61:         f.setSize(320, 200);
62:         f.setVisible(true);
63:         f.addWindowListener(new WindowAdapter() {
64:                 public void windowClosing(WindowEvent e) {
65:                     System.exit(0);
66:                 }
67:             });
68:     }
69: }
Aside from the classname changes from SecondCounterLockup to SecondCounterRunnable , the main difference from the preceding example is what goes on when the Start button is pressed. Just after startup, the application looks the same as before (refer to Figure 4.2 ), except for the minor detail of the text in the title bar. After the Start button is pressed, the new application looks similar to Figure 4.7.
Figure 4.7: Approximately 40 seconds after the Start button is pressed while running SecondCounterRunnableMain.
When the Start button is pressed, the actionPerformed() method is invoked (lines 1929) on the anonymous inner subclass of ActionListener (line 18). This method is invoked by the JavaVMs event handling thread. In there, the Start button is first disabled (line 21) to prevent any further pressing until the timer is stopped . Next, rather than directly call runClock() and tie up the event handling thread, a new Thread is instantiated using this constructor:
public Thread(Runnable target, String name )
A new Thread is created passing the reference to the SecondCounterRunnable component as the Runnable target for the Thread object to use (line 24). On this same line, a name for the thread is also passed in: SecondCounter . A new thread is spawned by invoking start() on this new Thread object (line 25). This new thread begins its execution asynchronously. The event handling thread proceeds to enable the Stop button (line 27) and requests that it have the focus (line 28). The event handling thread then returns from actionPerformed() and continues on with the business of handling other events as they come up.
Meanwhile, the new thread that was spawned enters the run() method of SecondCounterRunnable and calls runClock() . This newly spawned thread continues to be alive until it finds that keepRunning has been set to false , at which time it returns from runClock() , returns from run() , and dies.
When the Stop button is pressed, the other actionPerformed() method is invoked (lines 3338) on the anonymous inner subclass of ActionListener (line 32). All event handling methods , including this one, are invoked by the JavaVMs event handling thread. In there, the Stop button is first disabled (line 34) to prevent any further pressing until the timer is restarted. Next, the stopClock() method is invoked on the SecondCounterRunnable object. In there, the keepRunning flag is set to false to signal the while loop to terminate. The event handling thread returns from stopClock() and proceeds to enable the Start button and give it the focus (lines 36 and 37). The event handling thread then returns from actionPerformed() and returns to the JavaVMs event queue to wait for new events to occur.
If the Stop button is pressed after approximately 1 minute and 15 seconds have elapsed, the application looks like Figure 4.8.
Figure 4.8: Stop was pressed after the timer ran for approximately 75 seconds.

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