> ### Solution for
> Programming Exercise 7.6

> THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to the following exercise from this on-line Java textbook.

> Exercise 7.6: The `StopWatch` component from Section 7.4 displays the text "Timing..." when the stop watch is running. It would be nice if it displayed the elapsed time since the stop watch was started. For that, you need to create a `Thread`. Add a `Thread` to the original source code, StopWatch.java, to display the elapsed time in seconds. Create the thread in the `mousePressed()` routine when the timer is started. Stop the thread in the `mousePressed()` routine when the timer is stopped. The elapsed time won't be very accurate anyway, so just show the integral number of seconds. You only need to set the text a few times per second. In my `run()` method, I insert a delay of 100 milliseconds after I set the text. Here is an applet that tests my solution to this exercise:

> Discussion

> The applet follows guidelines for using threads from Section 7.5. There is a variable called `runner` that holds a reference to the `Thread` object. I create a new thread each time the timer is started. (An alternative would be to have a single thread. The thread would be awakened when the timer is started and would be suspended when the timer is stopped. There is no particular reason for preferring one of these alternatives to the other.) There is a `status` variable whose possible values are given by constants named `GO` and `TERMINATE`. The applet sets the value of `status` to `GO` before it creates the thread. The thread runs as long as the value of `status` is not `TERMINATE`. When it's time for the thread to die, the applet sets the value of `status` to `TERMINATE`. Since the `status` variable is shared, all access to the variable takes place in `synchronized` methods and statements. All this is standard procedure for working with threads. (My standard procedure, that is. There is no general standard that I know of.)

> The `run()` method that is executed by the thread just sets the text on the label about 10 times a second. (The text actually changes just once per second. Setting it 10 times per second ensures that the displayed value is reasonably accurate.) The elapsed time is computed by subtracting the time when the thread started from the current time. The 100 millisecond delay is accomplished by calling `waitDelay()`, a method that was given in Section 7.5. The `run()` method will end when the `status` changes to `TERMINATE`, and the thread will die.

>            public void run() {
>                   // Run method executes while the timer is going.
>                   // Several times a second, it computes the number of
>                   // seconds the thread has been running and displays the
>                   // whole number of seconds on the label.  It ends
>                   // when status is set to TERMINATE.
>               long start;  // Time when thread starts.
>               start = System.currentTimeMillis();
>               while (true) {
>                  synchronized(this) {
>                     if (status == TERMINATE)
>                        break;
>                     long time = (System.currentTimeMillis() - start) / 1000;
>                     setText( "Running:  " + time + " seconds");
>                  }
>                  waitDelay(100);
>               }
>            }

> (Programming Note: Originally, I didn't declare the variable "`start`" in the `run` method. Instead, I just used an instance variable, `startTime`, as the start time. This variable is set to the time when the user clicked the mouse, `evt.getWhen()`, in the `mousePressed()` method. This worked fine on my computer. But when I looked at the applet on another computer, the time was wrong! Apparently, the times used by the `System.currentTimeMillis()` and by `evt.getWhen()` were different. They used different times for time zero. According the Java documentation I have, this is incorrect behavoir. However, it's typical of Java programming that you have to code around problems on specific platforms. Unfortunately, although Java is supposed to be "write once, run anywhere", it doesn't quite work out that way.)

> The thread is started and stopped in the `mousePressed()` method. This method needs a way to tell whether the mouse click that it is processing is starting the timer or stopping it. In the original version, a boolean-valued variable named `running` was used to keep track of this. In this version, the thread variable, `runner`, is non-`null` when the timer is running and is `null` when the timer is not running. So I got rid of the old "`running`" variable and test the value of `runner` instead.

>            synchronized public void mousePressed(MouseEvent evt) {
>                   // React when user presses the mouse by
>                   // starting or stopping the timer.
>               if (runner == null) {
>                     // Since runner is null, the timer is not running.
>                     // Record the time and start the timer by creating a thread.
>                  startTime = evt.getWhen();  // Time when mouse was clicked.
>                  status = GO;
>                  runner = new Thread(this);
>                  runner.start();
>               }
>               else {
>                     // Stop the thread.  Compute the elapsed time since the
>                     // timer was started and display it.
>                  status = TERMINATE;
>                  notify();  // Wake up thread so it can terminate quickly.
>                  long endTime = evt.getWhen();
>                  double seconds = (endTime - startTime) / 1000.0;
>                  setText("Time:  " + seconds + " seconds");
>                  runner = null;
>               }
>            }

> If runner is `null`, the `mousePressed` method records the start time of the timer and it creates a new thread. The thread will almost immediately set the display of the label to "Running: 0 seconds". When the user clicks again, the value of `runner` is not null. The `mousePressed()` method stops the thread and it sets the text on the label to show the exact time that the timer was running. (It would have been possible to let the thread change the display as its last act. In that case, the `endTime` variable would have to be an instance variable so that the thread would have access to it.)

> Note that the `mousePressed()` method is synchronized. This is important since it accesses the shared variable, `status`. If it were not synchronized, the following sequence of events would be possible: (1) The thread tests `status` and finds that the value is `GO`. At that moment, the tread is interrupted. (2) The `mousePressed()` method changes the value of status to `TERMINATE` and sets the display to "Time...". (3) The thread resumes and goes on to change the display to "Running..." even though the timer has been stopped. Only then does it test the value of `status` again and see that the value has changed to `TERMINATE`. The label continues to show "Running..." instead of the final time. (Remember that "synchronized" does not mean "cannot be interrupted". It means, essentially, "cannot be interrupted by someone else who is synchronized on the same object". Both parties have to be synchronized.)

> The complete source code is shown below, followed by the source code for the little applet that tests the component.

> The Solution

> The improved StopWatch component:

>         /*
>            A component that acts as a simple stop-watch.  When the user clicks
>            on it, this component starts timing.  When the user clicks again,
>            it displays the time between the two clicks.  Clicking a third time
>            starts another timer, etc.  While it is timing, the label just
>            displays the whole number of seconds since the timer was started.
>         */

>         import java.awt.*;
>         import java.awt.event.*;

>         public class StopWatchRunner extends Label
>                                implements MouseListener, Runnable {

>            private Thread runner; // A thread that runs as long as the timer
>                                   // is running.  It sets the text in the label
>                                   // to show the elapsed time.  This variable is
>                                   // non-null when the timer is running and is
>                                   // null when it is not running.  (This is
>                                   // tested in mousePressed().)

>            private static final int        // Constants for use with status variable.
>                        GO = 0,
>                        TERMINATE = 1;

>            private int status;  // This variable is set by mousePressed()
>                                 // to control the thread.  When it's time
>                                 // for the thread to end, the value is
>                                 // set to TERMINATE.

>            private long startTime;   // Start time of timer.
>                                      //   (Time is measured in milliseconds.)

>            public StopWatchRunner() {
>                  // Constructor.  Call the constructor from the superclass
>                  // and listen for mouse clicks.
>               super("  Click to start timer.  ", Label.CENTER);
>               addMouseListener(this);
>            }

>            synchronized public void destroy() {
>                   // Before an applet that uses this component is
>                   // destroyed, it can call this method to make sure
>                   // that, if a timer thread is still running, it
>                   // will stop.  (This method is NOT called by the system!)
>                status = TERMINATE;
>            }

>            public void run() {
>                   // Run method executes while the timer is going.
>                   // Several times a second, it computes the number of
>                   // seconds the thread has been running and displays the
>                   // whole number of seconds on the label.  It ends
>                   // when status is set to TERMINATE.
>               long start;  // Time when thread starts.
>               start = System.currentTimeMillis();
>               while (true) {
>                  synchronized(this) {
>                     if (status == TERMINATE)
>                        break;
>                     long time = (System.currentTimeMillis() - start) / 1000;
>                     setText( "Running:  " + time + " seconds");
>                  }
>                  waitDelay(100);
>               }
>            }

>            synchronized void waitDelay(int milliseconds) {
>                  // Pause for the specified number of milliseconds OR
>                  // until the notify() method is called by some other thread.
>                  // (From Section 7.5 of the text.)
>               try {
>                  wait(milliseconds);
>               }
>               catch (InterruptedException e) {
>               }
>            }

>            synchronized public void mousePressed(MouseEvent evt) {
>                   // React when user presses the mouse by
>                   // starting or stopping the timer.
>               if (runner == null) {
>                     // Since runner is null, the timer is not running.
>                     // Record the time and start the timer by creating a thread.
>                  startTime = evt.getWhen();  // Time when mouse was clicked.
>                  status = GO;
>                  runner = new Thread(this);
>                  runner.start();
>               }
>               else {
>                     // Stop the thread.  Compute the elapsed time since the
>                     // timer was started and display it.
>                  status = TERMINATE;
>                  notify();  // Wake up thread so it can terminate quickly.
>                  long endTime = evt.getWhen();
>                  double seconds = (endTime - startTime) / 1000.0;
>                  setText("Time:  " + seconds + " seconds");
>                  runner = null;
>               }
>            }

>            public void mouseReleased(MouseEvent evt) { }
>            public void mouseClicked(MouseEvent evt) { }
>            public void mouseEntered(MouseEvent evt) { }
>            public void mouseExited(MouseEvent evt) { }

>         }  // end StopWatch

> A small applet to test the component:

>         /*
>            A trivial applet that tests the StopWatchTimer component.
>            The applet just creates and shows a StopWatchTimer.
>         */

>         import java.awt.*;
>         import java.applet.*;

>         public class TestStopWatchRunner extends Applet {

>            public void init() {

>               StopWatchRunner watch = new StopWatchRunner();
>               watch.setFont( new Font("SansSerif", Font.BOLD, 24) );
>               watch.setBackground(Color.white);
>               watch.setForeground( new Color(180,0,0) );
>               setBackground(Color.white);
>               setLayout(new BorderLayout() );
>               add(watch, BorderLayout.CENTER);

>            }

>         }

> [ Exercises | Chapter Index | Main Index ]