Section 7.6
Timers, Animation, and Threads


JAVA IS A MULTI-THREADED LANGUAGE, which means that several different things can be going on, in parallel. A thread is the basic unit of program execution. A thread executes a sequence of instructions, one after the other. When the system executes a stand-alone program, it creates a thread. (Threads are usually called processes in this context, but the differences are not important here.) The commands of the program are executed sequentially, from beginning to end, by this thread. The thread "dies" when the program ends. In a typical computer system, many threads can exist at the same time. At a given time, only one thread can actually be running, since the computer's Central Processing Unit (CPU) can only do one thing at a time. (An exception to this is a multi-processing computer, which has several CPUs. At a given time, every CPU can be executing a different thread.) However, the computer uses time sharing to give the illusion that several threads are being executed at the same time, "in parallel." Time sharing means that the CPU executes one thread for a while, then switches to another thread, then to another..., and then back to the first thread -- typically about 100 times per second. As far as users are concerned, the threads might as well be running at the same time.

To say that Java is a multi-threaded language means that a Java program can create one or more threads which will then run in parallel with the program. This is a fundamental, built-in part of the language, not an option or add-on like it is in some languages. Still, programming with threads can be tricky, and should be avoided unless it is really necessary. Ideally, even then, you can avoid using threads directly by using well-tested classes and libraries that someone has written for you.


Animation In Swing

One of the places where threads are used in GUI programming is to do animation. An animation is just a sequence of still images displayed on the screen one after the other. If the images are displayed quickly enough and if the changes from one image to the next are small enough, then the viewer will perceive continuous motion. To program an animation, you need some way of displaying a sequence of images.

A GUI program already has at least one thread, an event-handling thread, which detects actions taken by the user and calls appropriate subroutines in the program to handle each event. An animation, however, is not driven by user actions. It is something that happens by itself, as an independent process. In Java, the most natural way to accomplish this is to create a separate thread to run the animation. Before the introduction of the Swing GUI, a Java programmer would have to deal with this thread directly. This made animation much more complicated than it should have been. The good news in Swing is that it is no longer necessary to program directly with threads in order to do simple animations. The neat idea in Swing is to integrate animation with event-handling, so that you can program animations using the same techniques that are used for the rest of the program.

In Swing, an animation can be programmed using an object belonging to the class javax.swing.Timer. A Timer object can generate a sequence of events on its own, without any action on the part of the user. To program an animation, all your program has to do is create a Timer and respond to each event from the timer by displaying another frame in the animation. Behind the scenes, the Timer runs a separate thread which is responsible for generating the events, but you never need to deal with the thread directly.

The events generated by a Timer are of type ActionEvent. The constructor for a Timer specifies two things: The amount of time between events and an ActionListener that will be notified of each event:

            Timer(int delayTime, ActionListener listener)

The listener should be programmed to respond to events from the Timer in its actionPerformed() method. The delay time between events is specified in milliseconds (where one second equals 1000 milliseconds). The actual delay time between two events can be longer than the requested delay time, depending on how long it takes to process the events and how busy the computer is with other things. In a typical animation, somewhere between ten and thirty frames should be displayed every second. These rates correspond to delay times between 100 and 33.

A Timer does not start running automatically when it is created. To make it run, you must call its start() method. A timer also has a stop() method, which you can call to make it stop generating events. If you have stopped a timer and want to start it up again, you can call its restart() method. (The start() method should be called only once.) None of these methods have parameters.

Let's look at an example. In the following applet, you can start an animation running by clicking the "Start" button. When you do this, the text on the button changes to "Stop", and you can stop the animation by clicking the button again. This is yet another applet that says "Hello World." The animation simply cycles the color of the message through all possible hues:

(Applet "HelloWorldSpectrum" would be displayed here
if Java were available.)

Here is part of the source code for this applet, omitting the definition of the nested class that defines the drawing surface:


   public class HelloWorldSpectrum extends JApplet {


      Display display;  // A JPanel belonging to a nested "Display"
                        // class; used for displaying "Hello World."
                        // It defines a method "setColor(Color)" for
                        // setting the color of the displayed message.

      JButton startStopButton; // The button that will be used to 
                               // start and stop the animation.

      Timer timer;  // The timer that drives the animation.  A timer
                    // is started when the user starts the animation.
                    // Each time an ActionEvent is received from the
                    // timer, the color of the message will change.
                    // The value of this variable is null when the
                    // animation is not in progress.

      int colorIndex;  // This is be a number between 0 and 100 that
                       // will be used to determine the color.  It will
                       // increase by 1 each time a timer event is
                       // processed.

      public void init() {
             // This is called by the system to initialize the applet.
             // It adds a button to the "south" position in the applet's
             // content pane, and it adds a display panel to the "center"
             // position so that it will fill the rest of the content pane.

          display = new Display();
                // The component that displays "Hello World".

          getContentPane().add(display, BorderLayout.CENTER);
                // Adds the display panel to the CENTER position of the
                // JApplet's content pane.

          JPanel buttonBar = new JPanel();
                // This panel will hold the button and appears
                // at the bottom of the applet.
          buttonBar.setBackground(Color.gray);
          getContentPane().add(buttonBar, BorderLayout.SOUTH); 

          startStopButton = new JButton("Start");
          buttonBar.add(startStopButton);

          startStopButton.addActionListener( new ActionListener() {
                     // The action listener that responds to the 
                     // button starts or stops the animation.  It
                     // checks the value of timer to find out which
                     // to do.  Timer is non-null when the animation
                     // is running, so if timer is null, the 
                     // animation needs to be started.
                public void actionPerformed(ActionEvent evt) {
                   if (timer == null)
                      startAnimation();
                   else
                      stopAnimation();
                }
             });

      }  // end init()

      ActionListener timerListener = new ActionListener() {
               // Define an action listener to respond to events
               // from the timer.  When an event is received, the
               // color of the display is changed.
            public void actionPerformed(ActionEvent evt) {
                colorIndex++;  // A number between 0 and 100.
                if (colorIndex > 100)
                   colorIndex = 0;
                float hue = colorIndex / 100.0F;  // Between 0.0F and 1.0F.
                display.setColor( Color.getHSBColor(hue,1,1) ); 
            }
         };


      void startAnimation() {
             // Start the animation, unless it is already running.
             // We can check if it is running since the value of
             // timer is non-null when the animation is running.
             // (It should be impossible for this to be called
             // when an animation is already running... but it
             // doesn't hurt to check!)
          if (timer == null) {
                // Start the animation by creating a Timer that
                // will fire an event every 50 milliseconds, and 
                // will send those events to timerListener.
             timer = new Timer(50, timerListener);
             timer.start();  // Make the time start running.
             startStopButton.setText("Stop");
          }
      }


      void stopAnimation() {
            // Stop the animation by stopping the timer, unless the
            // animation is not running.
         if (timer != null) {
            timer.stop();   // Stop the timer.
            timer = null;   // Set timer variable to null, so that we
                            //   can tell that the animation isn't running.
            startStopButton.setText("Start");
         }
      }


      public void stop() {
            // The stop() method of an applet is called by the system
            // when the applet is about to be stopped, either temporarily
            // or permanently.  We don't want a timer running while
            // the applet is stopped, so stop the animation.  (It's
            // harmless to call stopAnimation() if the animation is not
            // running.)
         stopAnimation();
      }
         .
         .
         .

This applet responds to ActionEvents from two sources: the button and the timer that drives the animation. I decided to use a different ActionListener object for each source. Each listener object is defined by an anonymous nested class. (It would, of course, be possible to use a single object, such as the applet itself, as a listener, and to determine the source of an event by calling evt.getSource(). However, Java programmers tend to be fond of anonymous classes.)

The applet defines methods startAnimation() and stopAnimation(), which are called when the user clicks the button. Each time the user clicks "Start", a new timer is created and started:

            timer = new Timer(50,timerListener);
            timer.start();

Remember that without timer.start(), the timer won't do anything at all. The constructor specifies a delay time of 50 milliseconds, so there should be about 20 action events from the timer every second. The second parameter is an ActionListener object. The timer events will be processed by calling the actionPerformed() method of this object. The stopAnimation() method calls timer.stop(), which ends the flow of events from the timer. The startAnimation() and stopAnimation() methods also change the text on the button, so that it reads "Stop" when the animation is running and "Start" when it is not running.

The actionPerformed() method in timerListener responds to a timer event by setting the color of the display. The color is computed from a number, colorIndex that is changed for each frame. (The color is specified as an "HSB" color. See Section 6.3 for information on the HSB color system.)


JApplet's start() and stop() Methods

There is one other method in the HelloWorldSpectrum class that needs some explanation: the stop() method. Every applet has several methods that are meant to be called by the system at various times in the applet's life cycle. We have already seen that init() is called when the applet is first created, before it appears on the screen. Another method, destroy() is called just before the applet is destroyed, to give it a chance to clean things up. Two other applet methods, start() and stop(), are called by the system between init() and destroy() The start() method is always called by the system just after init(), and stop() is always called just before destroy(). However, start() and stop() can also be called at other times. The reason is that an applet is not necessarily active for the whole time that it exists.

Suppose that you are viewing a Web page that contains an applet, and suppose you follow a link to another page. The applet still exists. It will be there if you hit your browser's Back button to return to the page that contains the applet. However, the page containing the applet is not visible, so the user can't see the applet or interact with it. The applet will not receive any events from the user, and since it is not visible, it will not be asked to paint itself. The system calls the applet's stop() method when user leaves the page that contains the applet. The system will call the applet's start() method if the user returns to that page. This lets the applet keep track of when it is active and when it is inactive. It might want to do this so that it can avoid using system resources when it is inactive.

In particular, an applet should probably not leave a timer running when it is inactive. The HelloWorldSpectrum applet defines the stop() method to call stopAnimation(), which, in turn, will stop the timer if it is running. When the applet is about to become inactive, the system will call stop(), and the animation -- if it was running -- will be stopped. You can try it, if you are reading this page in a Web browser: Start the animation running in the above applet, go to a different page, and then come back to this page. You should see that the animation has been stopped.

The animation in the HelloWorldSpectrum applet is started and stopped under the control of the user. In many cases, we want an animation to run for the entire time that an applet is active. In that case, the animation can be started in the applet's start() method and stopped in the applet's stop() method. It would also be possible to start the animation in the init() method and stop it in the destroy() method, but that would leave the animation running, uselessly, while the applet is inactive. In the following example, a message is scrolled across the page. It uses a timer which churns out events for the whole time the applet is active:

(Applet "ScrollingHelloWorld" would be displayed here
if Java were available.)

You can find the source code in the file ScrollingHelloWorld.java, but the relevant part here is the start() and stop() methods:


      public void start() {
            // Called when the applet is being started or restarted.
            // Create a new timer, or restart the existing timer.
         if (timer == null) {
               // This method is being called for the first time,
               // since the timer does not yet exist.
            timer = new Timer(300, this);  // (Applet listens for events.)
            timer.start();
         }
         else {
            timer.restart();
         }
      }


      public void stop() {
            // Called when the applet is about to be stopped.
            // Stop the timer.
         timer.stop();
      }

These methods can be called several times during the lifetime of an applet. The first time start() is called, it creates and starts a timer. If start() is called again, the timer already exists, and the existing timer is simply restarted.


Other Useful Timer Methods

Although timers are most often used to generate a sequence of events, a timer can also be configured to generate a single event, after a specified amount of time. In this case, the timer is being used as an alarm which will signal the program after a specified amount of time has passed. To use a Timer object in this way, call setRepeats(false) after constructing it. For example:

            Timer alarm = new Timer(5000, listener);
            alarm.setRepeats(false);
            alarm.start();

The timer will send one action event to the listener five seconds (5000 milliseconds) after it is started. You can cancel the alarm before it goes off by calling its stop() method.

Here's one final way to control the timing of events: When you start a repeating timer, the time until the first event is the same as the time between events. Sometimes, it would be convenient to have a longer or shorter delay before the first event. If you start a timer in the init() method of an applet, for example, you might want to give the applet some time to appear on the screen before receiving any events from the timer. You can set a separate delay for the first event by calling timer.setInitialDelay(delay), where timer is the Timer and delay is specified in milliseconds as usual.


Using Threads

Although Timers can replace threads in some cases, there are times when direct use of threads is necessary. When a Timer is used, the processing is done in an event handler. This means that the processing must be something that can be done quickly. An event handler should always finish its work quickly, so that the system can move on to handle the next event. If an event handler runs for a long time, it will block other events from being processed, and the program will become unresponsive. So, in a GUI application, any computation or process that will take a long time to complete should be run in a separate thread. Then the event-handling thread can continue to run at the same time, responding to user actions as they occur.

As a short and incomplete introduction to threads, we'll look at one example. The example requires some explanation, but the main point for our discussion of threads is that it is a realistic example of a long computation that requires a separate thread. The example is based on the Mandelbrot set, a mathematical curiosity that has become familiar because it can be used to produce a lot of pretty pictures. The Mandelbrot set has to do with the following simple algorithm:

       Start with a point (x,y) in the plane, where x and y are real numbers.
       Let zx = x, and let zy = y.
       Repeat the following:
           Replace (zx,zy) with ( zx*zx - zy*zy + x, 2*zx*zy + y )

The question is, what will happen to the point (zx,zy) as the loop is repeated over and over? The answer depends on the initial point (x,y). For some initial points (x,y), the point (zx,zy) will, sooner or later, move arbitrarily far away from the origin, (0,0). For other starting points, the point (zx,zy) will stay close to (0,0) no matter how many times you repeat the loop. The Mandelbrot set consists of the (x,y) points for which (zx,zy) stays close to (0,0) forever. This would probably not be very interesting, except that the Mandelbrot set turns out to have an incredibly intricate and quite pretty structure.

To get a pretty picture from the Mandelbrot set, we change the question, just a bit. Given a starting point (x,y), we ask, how many steps does it take, up to some specified maximum number, before the point (zx,zy) moves some set distance away from (0,0)? We then assign the point a color, depending on the number of steps. If we do this for each (x,y), we get a kind of picture of the set. For a point in the Mandelbrot set, the count always reaches the maximum (since for such points, (zx,zy) never moves far away from zero). For other points, in general, the closer the point is to the Mandelbrot set, the more steps it will take.

With all that said, here is an applet that computes a picture of the Mandelbrot set. It will begin its computation when you press the "Start" button. (Eventually, the color of every pixel in the applet will be computed, but the applet actually computes the colors progressively, filling the applet with smaller and smaller blocks of color until it gets down to the single pixel level.) The applet represents a region of the plane with -1.25 <= x <= 1.0 and -1.25 <= y <= 1.25. The Mandelbrot set is colored purple. Points outside the set have other colors. Try it:

(Applet "Mandelbrot" would be displayed here
if Java were available.)

The algorithm for computing the colors in this applet is:

       For square sizes 64, 32, 16, 8, 4, 2, and 1:
          For each square in the applet:
             Let (a,b) be the pixel coords of the center of the square.
             Let (x,y) be the real numbers corresponding to (a,b).
             Let (zx,zy) = (x,y).
             Let count = 0.
             Repeat until count is 80 or (zx,zy) is "big":
                 Let new_zx = zx*zx - zy*zy + x.
                 Let zy = 2*zx*zy + y.
                 Let zx = new_zx.
                 Let count = count + 1.
             Let color = Color.getHSBColor( count/100.0F, 0.0F, 0.0F )
             Fill the square with that color.

The point is that this is a long computation. When you click the "Start" button of the applet, the applet creates a separate thread to do this computation.

In Java, a thread is an object belonging to the class java.lang.Thread. The purpose of a thread is to execute a single subroutine from beginning to end. In the Mandelbrot applet, that subroutine implements the above algorithm. The subroutine for a thread is usually an instance method

                 public void run()

that is defined in an object that implements the interface named Runnable. The Runnable interface defines run() as its only method. The Runnable object is provided as a parameter to the constructor of the thread object. (It is also possible to define a thread by declaring a subclass of class Thread, and defining a run() method in the subclass, but it is more common to use a Runnable object to provide the run() method for a thread.) If runnableObject is an object that implements the Runnable interface, then a thread can be constructed with the command:

            Thread runner = new Thread(runnableObject);

The job of this thread is to execute the run() method in runnableObject. Just as with a Timer, it is not enough to construct a thread. You must also start it running by calling its start method: runner.start(). When this method is called, the thread will begin executing the run() method in the runnableObject, and it will do this in parallel with the rest of the program. When the subroutine ends, the thread will die, and it cannot be restarted or reused. There is no stop method for stopping a thread. (Actually, there is one, but it is deprecated, meaning that you are not supposed to call it.) If you want to be able to stop the thread, you need to provide some way of telling the thread to stop itself. I often do this with an instance variable named running that is visible both in the run() method and elsewhere in the program. When the program wants the thread to stop, it just sets the value of running to false. In the run() method, the thread checks the value of running regularly. If it sees that the value of running has become false, then the run() method should end. Here, for example, are the methods that the Mandelbrot applet uses to start and to stop the thread:

      void startRunning() {
           // A simple method that starts the computational thread,
           // unless it is already running.  (This should be
           // impossible since this method is only called when
           // the user clicks the "Start" button, and that button
           // is disabled when the thread is running.)
         if (running)
            return;
         runner = new Thread(this);
              // Creates a thread that will execute the run()
              // method in this class, which implements Runnable.
         running = true;
         runner.start();
      }
      
      void stopRunning() {
           // A simple method that is called to stop the computational
           // thread.  This is done by setting the value of the
           // variable, running.  The thread checks this value
           // regularly and will terminate when running becomes false.
         running = false;
      }

There are a few more details of threads that you need to understand before looking at the run() method from the applet. First, on some platforms, once a thread starts running it grabs control of the CPU, and no other thread can run until it yields control. In Java, a thread can yield control, and allow other threads to run, by calling the static method Thread.yield(). I do this regularly in the run() method of the applet. If I did not do this, then, on some platforms, the computational thread would block the rest of the program from running. Another way for a thread to yield control is to go to sleep for a specified period of time by calling Thread.sleep(time), where time is the number of milliseconds for which the thread will be inactive. The thread in a Timer, for example, sleeps between events, since it has nothing else to do.

Another issue concerns the use of threads with Swing. There is a rule in Swing: Don't touch any GUI components, or any data used by them, except in the event-handling thread, that is, in event-handling methods or in paintComponent(). The reason for this is that Swing is not thread-safe. If more than one thread plays with Swing's data structures, then the data can be corrupted. (This is done for efficiency. Making Swing thread-safe would slow it down significantly.) To solve this problem, Swing has a way for another thread to get the event-handling thread to run a subroutine for it. The subroutine must be a run() method in a Runnable object. When a thread calls the static method

            void SwingUtilities.invokeAndWait(Runnable runnableObject)

the run() method of runnableObject will be executed in the event-handling thread, where it can safely do anything it wants to Swing components and their data. The invokeAndWait method, as the name indicates, does not return until the run() method has been executed. (The invokeAndWait method can produce an exeception, and so must be called inside a try...catch statement. I will cover try...catch statements in Chapter 9. You can ignore it for now.)

The run() method for the thread in the Mandelbrot applet uses SwingUtilities.invokeAndWait() to color a square on the screen. Here, finally, is that run() method (which will still take some work to understand):

      public void run() {
            // This is the run method that is executed by the
            // computational thread.  It draws the Mandelbrot
            // set in a series of passes of increasing resolution.
            // In each pass, it fills the applet with squares
            // that are colored to represent the Mandelbrot set.
            // The size of the squares is cut in half on each pass.

         startButton.setEnabled(false);  // Disable "Start" button
         stopButton.setEnabled(true);    //    and enable "Stop" button
                                         //    while thread is running.

         int width = getWidth();   // Current size of this canvas.
         int height = getHeight();

         OSI = createImage(getWidth(),getHeight());
             // Create the off-screen image where the picture will
             // be stored, and fill it with black to start.
         OSG = OSI.getGraphics();
         OSG.setColor(Color.black);
         OSG.fillRect(0,0,width,height);
         
         for (size = 64; size >= 1 && running; size = size/2) {
               // Outer for loop performs one pass, filling
               // the image with squares of the given size.
               // The size here is given in terms of pixels.
               // Note that all loops end immediately if running
               // becomes false.
            double dx,dy;  // Size of square in real coordinates.
            dx = (xmax - xmin)/width * size;
            dy = (ymax - ymin)/height * size;
            double x = xmin + dx/2;  // x-coord of center of square.
            for (i = size/2; i < width+size/2 && running; i += size) {
                  // First nested for loop draws one column of squares.
               double y = ymax - dy/2; // y-coord of center of square
               for (j = size/2; j < height+size/2 && running; j += size) {
                      // Innermost for loop draws one square, by
                      // counting iterations to determine what
                      // color it should be, and then invoking the
                      // "painter" object to actually draw the square.
                   colorIndex = countIterations(x,y);
                   try {
                      SwingUtilities.invokeAndWait(painter);
                   }
                   catch (Exception e) {
                   }
                   y -= dy;
               }
               x += dx;
               Thread.yield();  // Give other threads a chance to run.
            }
         }

         running = false;  // The thread is about to end, either
                           // because the computation is finished
                           // or because running has been set to
                           // false elsewhere.  In the former case,
                           // we have to set running = false here
                           // to indicate that the thread is no
                           // longer running.

         startButton.setEnabled(true);  // Reset states of buttons.
         stopButton.setEnabled(false);

      } // end run()

You can find the full source code in the file Mandelbrot.java.

There is a lot more to be said about threads. I will not cover it all in this book, but I will return to the topic in Section 10.5.


[ Next Section | Previous Section | Chapter Index | Main Index ]