Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Programming Java threads in the real world, Part 5

Has Sun abandoned 'run anywhere'? Plus: Threads and Swing, timers, and getting around stop(), suspend(), and resume() deprecation

  • Print
  • Feedback

Page 4 of 5

Don't be misled by the fact that you're using a Runnable object to pass a request to Swing. The run() method doesn't run on its own thread; it uses the Swing event queue. If run() takes a long time to execute, your program's user interface will appear to lock up -- it won't respond at all to any user input. It's best for these methods either to be very short or to fire up their own threads to do time-consuming background operations.

Using the Swing Timer

One common situation where a standalone thread needs to talk to Swing is an animation loop. Listing 1 below shows how you might be tempted to implement an animation loop. This is typical AWT-style code, where the run() method of a thread (line 42) forces a repaint at a regular interval. The thread is fired up in start() (line 23). Unfortunately, this code only appears to work -- it will probably run fine until you try to actually use an Animator1 object in a real application that uses Swing intensively, then it will blow up. The run() method on line 42 of Listing 1 runs on the thread created on line 24, not on the Swing thread. Consequently, the call to repaint on line 51 is not safe. We're calling an unsynchronized Swing method from somewhere other than a listener method or the Swing event processing thread.

Also, the animation loop is a bit jerky because the repaint doesn't actually happen on a fixed interval. There's a difference between repainting every 100 milliseconds and waiting 100 milliseconds between repaint requests. In the latter case, which is what I'm doing here, the actual time between refreshes can vary, depending on how long it takes for repaint() to execute. That's not really the behavior I want.



Listing 1. (Animator1.java) The wrong way to do animation




01  
02  
03  
04  
05  
06  
07  
08  
09  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  


import java.awt.*;
import javax.swing.*;
public class Animator1 extends JComponent implements Runnable
{
   private final int     refresh_rate;
   private final Image[] frames;
   private       int     current_frame = 0;
   private       Thread  driver;
    //---------------------------------------------------
   public Animator1( Image[] frames, int refresh_rate )
                                            throws InterruptedException
    {
        this.frames       = frames;
        this.refresh_rate = refresh_rate;
        MediaTracker tracker = new MediaTracker(this);  // wait for all
        for( int i = 0; i < frames.length; ++i )        // images to 
            tracker.addImage( frames[i], 0 );           // load
        tracker.waitForAll();
    }
    //---------------------------------------------------
   public void start()
   {   driver = new Thread( this );
        driver.start();
    }
    //---------------------------------------------------
   public void stop()
    {   driver.interrupt();
    }
    //---------------------------------------------------
   public Dimension getPreferredSize()
    {   return new Dimension(   frames[0].getWidth(this),
                                frames[0].getHeight(this) );
    }
    //---------------------------------------------------
   public void paint( Graphics g )
    {   g.drawImage( frames[ current_frame ], 0, 0,
                        getBounds().width, getBounds().height, this );
    }
    //---------------------------------------------------
   public void run()
    {   try
        {   while( !Thread.interrupted() )
            {
                Thread.currentThread().sleep( refresh_rate );
                ++current_frame;
                current_frame %= frames.length;
               Animator1.this.repaint();
            }
        }
        catch( InterruptedException e ){/*terminate thread by returning*/}
    }
    //==================================================================
   public static class Test extends JFrame
    {
       static public void main( String[] s ) throws Exception
        {
            Toolkit kit     = Toolkit.getDefaultToolkit();
            Image[] images  = new Image[]
            {
                kit.getImage("5.images/Juggler0.gif"),
                kit.getImage("5.images/Juggler1.gif"),
                kit.getImage("5.images/Juggler2.gif"),
                kit.getImage("5.images/Juggler3.gif"),
                kit.getImage("5.images/Juggler4.gif") 
            };
            Animator1 animator = new Animator1( images, 100 );
            JFrame frame = new Test();
            frame.getContentPane().add( animator );
            frame.pack();
            frame.show();
            animator.start();
            Thread.currentThread().sleep( 5 * 1000 );
            animator.stop();
            System.exit(0);
        }
    }
}






Killing threads

You should pay attention to how run() terminates in Listing 1. Jeff Richter, in his book, Advanced Windows Programming, says that there are three ways in which threads can die: they can die a natural death, commit suicide, or be murdered! Java's stop() method, use of which falls in both the murder and suicide category, is now deprecated because it can leave NT DLLs in an unstable state, so "murder" and "suicide" aren't options. The remaining option, a "natural death," is brought about by returning from run(). If you want to shut a thread down, you have to ask the thread to shut itself down.

A simple approach might simply call a method that sets an internal flag that's tested in the main run() loop of the thread. This approach doesn't usually work, however, because most threads spend the bulk of their time blocked, waiting for some operation like a blocking read to complete. A suspended thread can't test a flag. However, sending a blocked thread an interrupt() message will bump it out of the wait (with an exception toss). The stop() method (line 28) does just that: it sends an interrupt() message to the driver thread in order to stop it. (Remember, stop() itself runs on whatever thread calls it, not on the animator thread. The only methods that run on the animator thread are the ones that Animator.run() calls.) Since I intend for an interrupt operation to kill the thread, the run() method (line 42) catches the exception and ignores it.

What if the thread is interrupted, rather than blocked, while it's working? The Thread class provides two methods to check whether or not a thread has been interrupted. Thread.interrupted(), which I've used here, returns true if the current thread has been interrupted. A call to some_thread.isInterrupted() returns true if some_thread has been interrupted. It's a good idea to call one or the other of these periodically as the thread works.

Unit tests

The final thing to look at in Listing 1 is the unit-test class: Test, on line 57. A unit test is a piece of code that tests one thing, in this case a class. It's a good idea to associate a unit test with every class you write, and there are several ways to do that. You could pair a test file with every class file, but you'd end up with too many files to maintain if you did that, and keeping the test routine in phase with the class as it evolves would be difficult since you could recompile the class without recompiling the test.

Another strategy is to put a main() in every class. I don't do that because I don't want to carry around the baggage of a unit test in every class file that comprises the program. The tests are often bigger than the class I'm testing, and I don't want to inflate the application size with all this test code.

A third option -- putting the test code in a separate class, but in the same file as the class being tested -- doesn't work out very well because the package-level name space becomes corrupted with all the names of the test classes. Just managing these names can become a big problem.

  • Print
  • Feedback

Resources
  • All the real code discussed in this article (the stuff in the com.holub.asynch package) is available in the "Goodies" section on my Web site, http://www.holub.com. The version on the Web site should be considered the definitive version -- at least it corrects any bugs I know about.
  • An article describing Swing's support for threads is at http://java.sun.com/products/jfc/tsc/archive/tech_topics_arch/threads/threads.html
  • You can find documentation for the Swing Timer class in the JDK docs and at http://java.sun.com/products/jdk/1.2/docs/api/javax/swing/Timer.html
  • UML (the Unified Modeling Language) amalgamates the notations of Grady Booch, James Rumbaugh, and Ivar Jacobson. The following three references are recommended:
  • Martin Fowler and Kendall Scott's UML DistilledApplying the the Object Modeling Language (ReadingAddison Wesley, 1997 [ISBN0-201-32563-2]) is a good, quick introduction to UML for those who already know an object-oriented notationhttp://cseng.awl.com/bookdetail.qry?ISBN=0-201-32563-2&ptype=0
  • A more in-depth introduction to UML is Grady Booch, James Rumbaugh, Ivar Jacobson's The Unified Modeling Language User Guide (ReadingAddison Wesley, 1999 [ISBN0-201-57168-4])http://cseng.aw.com/bookdetail.qry?ISBN=0-201-57168-4&ptype=0