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

1 2 3 4 5 Page 3
Page 3 of 5

The main problems, however, are the synchronization problems discussed last month in the context of AWT: the actionPerformed() methods run on Swing's event thread, which typically is not the thread that's waiting for the time to expire. This means you must remember to synchronize the listener on the outer-class object (as I've done on line 17 of Listing 2). You must also use condition variables or a wait-notify strategy to synchronize the waiting thread and the notifying thread. For all the reason I discussed last month, it's this last set of problems that are the most bothersome in practice. That's why I didn't use Swing's architecture when I came up with my own timer class.

Roll your own

My own timer class, called Alarm, is different from the Swing Timer in several ways. First, it doesn't have to use the "observer" pattern at all. You can simply wait for the timer to expire by calling a blocking function that suspends the calling thread until the next timer tick occurs. This strategy has two advantages when compared to Swing's Timer: First, it's more in line with an object-oriented way of thinking. In object-oriented systems, you tend to think in terms of objects sending each other synchronous messages (which don't return until they've finished doing whatever they do) or asynchronous messages (which return immediately, while whatever they do goes on in the background). You're thinking messages here, not threads. Second, it's easier to code my Alarm than a Swing Timer. You don't have to worry about the listener-related synchronization problems, and you don't have to mess with wait() and notify() or a condition variable to find out when the timer has ticked. The Swing approach makes more sense when you can do everything you need to do in the actionPerformed() method, but I've found that most of the applications for timers I've come up with lend themselves more to a blocking-wait strategy.

Here's the earlier print-the-time-once-a-second example from Listing 2, rewritten to use my Alarm class:

  static public void main( String[] args )
    {
        Alarm clock = new Alarm(1000, Alarm.CONTINUOUS);
        clock.start();
        for( int i = 3; --i>= 0; )
        {   System.out.println( new Date().toString() );
            clock.await( Alarm.FOREVER );
        }
        t.stop();
    }

Note that the clock is running while the Date() object is being created and printed. The time required for this operation isn't added to the 1000-millisecond interval specified in the constructor call. If the loop takes longer than 1,000 milliseconds to execute, the program will miss a timer tick. Contrast this with the Swing approach, where timer ticks can be missed because the I/O system is too busy to notify the listeners. It's not that one situation is better or worse than the other, but there is a difference.

To my mind, the code that uses an Alarm is both easier to write and easier to read than that of Listing 2. It also doesn't require any condition variables for synchronization, because the Alarm object isn't sending messages to the main thread asynchronously; rather, the main thread simply waits for the timer to expire with a blocking call. There are still two threads, but the timer thread is doing nothing but timing -- it has no access to the code in the class that contains main(). Therefore, the main thread does not have to synchronize access to any fields because only one thread accesses those fields.

If you really need Swing-style event-based notification, the Alarm class supports that as well. Just add an ActionListener to the thread with a call to addActionListener(), and remove the listener with a call to removeActionListener(). Here's an example:

    clock = new Alarm( 1000 );  // 1-second continuous timer
    Alarm.Notifier notifier = new Alarm.Notifier( clock );
    notifier.addActionListener
    (   new ActionListener()
       {   public void ActionPerformed()
            {   System.out.println( new Date().toString() );
            }
        }
    );
    clock.start();

When you use an ActionListener, a second notification thread is created solely to notify the listeners. This second thread sleeps, waiting for the Alarm to go off, then notifies the listeners, then goes back to sleep. The notification thread destroys itself automatically when there are no more listeners to notify. Since a second thread is used for notification, the time spent executing actionPerformed() methods will not impact the Alarm's timing. Since the Alarm doesn't use the Swing thread to run these listener methods, you don't have to worry about locking up the UI if a listener takes too long to do its work. But, by the same token, you do have to call SwingUtilities.invokeLater() or SwingUtilities.invokeAndWait() from your ActionPerformed() method to safely talk to Swing.

The notification thread is a "daemon." The fact that it's running will not keep the program alive. The Alarm itself has a reference to it, however, so as long as you have a reference to the Alarm object with which listeners have been registered, the notifier thread will hang around.

How does an Alarm work?

The source code for the Alarm class is in Listing 4, below. There's a lot to talk about here. Starting from the top, I've declared references for the time between ticks, and the actual timer thread, clockListing 4, line 24). This thread spends most of its time suspended, waiting for the time interval to expire. I also declare a flag is_stopped (line 25) so I can tell the difference between a timer that's expired and one that's been stopped by the user. I need this flag because threads that are waiting on the Alarm are notified both on expiration and when the Alarm is sent a stop() method. This flag lets me return different values from wait(), so the waiting threads can distinguish the normal-expiration case from the timer-being-stopped case.

The next few lines define constants that control the way an Alarm works. Alarms run in one of three modes: CONTINUOUS, ONE_SHOT, and MULTI_SHOT, as defined symbolically on lines 29, 31, and 33, respectively. In CONTINUOUS mode, the Alarm runs continuously, firing off events (or releasing any waiting threads) at regular intervals. A ONE_SHOT Alarm runs only once, then shuts itself down, freeing all support-level threads. You can restart the timer by calling start() a second time. A MULTI_SHOT timer works like just like a ONE_SHOT; it doesn't free the underlying timer thread, however. Use a MULTI_SHOT when you know you're going to fire up the Alarm again and don't want to incur the thread-creation overhead.

All three of these symbolic constants are instances of the Mode class, defined on line 27. This class demonstrates the proper way to do an enumerated type in Java. Because of the private constructor, only three instances of Mode will ever exist: Alarm.CONTINUOUS, Alarm.ONE_SHOT, and Alarm.MULTI_SHOT. Consequently, the values of a Mode are constrained, as is only proper for an enumerated type. Since the Mode class itself is public, other classes can access a Mode, declare references to Mode objects, and so on. But the private constructor prevents other classes from creating a new Mode. A method can now take a Mode argument (as does the constructor on line 48), and the compiler -- not the runtime system -- can guarantee that that argument will be either a legitimate mode or null. There are no other possibilities. Had I used a static final int to define the modes, it would be possible to pass the constructor an illegal value for the Mode argument.

Two more symbolic constants, STOPPED and EXPIRED, are defined on lines 42 and 44. These are the "action command" Strings attached to the ActionEvent objects sent to registered listeners when the timer goes off. The action commands are outputs from the Alarm (as compared to inputs), the values are effectively constants, and a full-blown enumeration like Mode isn't required here.

To understand Alarm's methods, we need to skip ahead a moment and talk about structure. Three classes are used here, shown in Figure 1. (This is a UML static-model diagram. See the Resources for more information if you've never seen one of these before.)

Figure 1: Static model for the Alarm class

What time is it?

The main class is the Alarm itself, and it's this class that the client waits on. The Alarm isn't actually a thread, however, though it uses two threads (Clock and Notifier) to get its work done. The Clock (Listing 4, line 156) is what's actually doing the timing. It's run() method (line 166) loops, sleeping for the amount of time specified in delay (line 23. When it wakes up, it sends a notifyAll() message to the Alarm object, which in turn causes await() (line 97) to be released from its wait() and return. The Clock object then either goes back to sleep, destroys itself (line 197), or suspends itself ( line 185), depending on the operating mode.

There are a couple of reasons for using a second class for the timer thread (as compared to making the Alarm itself a Thread). The main reason is that occasionally it's convenient to wait on a timer that hasn't been started yet. For example, you might have a thread that performs some sort of timeout action that permanently waits on a MULTI_SHOT Alarm whose delay interval is the timeout time. The Alarm isn't fired up until the event is initiated, however. Using two objects lets me hold off on creating the timing thread until it's actually needed. The second reason for the two-object approach is that it makes it much easier to restart the timer before it's expired. Let's say you have a "dead-man" Alarm (my term) that works like a "dead-man switch." The thread that's waiting on the timer wakes up only if nothing's happened in the last N minutes. You can use this sort of thing for automatic saves to file, bringing up screen savers, and so on. You can implement a dead man by creating an Alarm with an N-second interval, then firing off a thread that waits on the alarm. The main program sends a start() message to the Alarm every time it does something. The Alarm doesn't go off until N seconds have passed without anything having been done.

Implementing this sort of restart with a single class is difficult because there's no way to change the time associated with a sleep() call without breaking out of the sleep. Using two classes is easy, though. The Alarm's start() method (line 59) simply interrupts the existing Clock, then creates a new one with the original delay. The notifications_off flag (line 159) is used by run() to make sure waiting threads aren't notified when a Clock is being killed during a reset operation.

There is one tricky synchronization issue in this code. The Clock removes all traces of itself as it shuts down by setting the Alarm's Clock reference to null, as seen on line 197. Meanwhile, several methods of the Alarm itself use or modify Clock. All the relevant Alarm methods are synchronized, so there's nothing to worry about there. The Clock object's run() method must synchronize on the outer-class object with a synchronized(Alarm.this) before it can safely be modified on line 197. By the same token, the Clock has to be synchronized on the outer-class object when it notifies the waiting threads on line 180. However, the Clock cannot hold the monitor lock on the Alarm object while it's suspended, as on line 185. That's a classic nested-monitor lockout (discussed back in Part 2 of this series.) The only way to bump the Clock out of the wait is to call the Alarm's start() method, which is synchronized on the same lock that's held by the dormant Clock thread. The solution is to release the outer-class object's lock while we're waiting on line 185. That's why I need two synchronized(Alarm.this) blocks.

Also note that I've used a wait() on line 185 rather than a (deprecated) suspend() call. The matching notify() is down on line 215. A wait()/notify() strategy can almost always replace calls to suspend() and resume(), without any of the monitor-related problems caused by the now-deprecated methods. I discussed these back in Part 2, but by way of reminder, suspend(), unlike wait(), does not give up the monitor's lock when it's called. This was the guy-goes-into-the-bathroom-and-falls-into-a-drug-induced-coma scenario. As a consequence, it's possible to get into deadlock scenarios if you use suspend() that you simply can't get into if you use wait(), which gives up the monitor's lock before it goes off to wait on the associated condition variable.

Also, note that the lock on the Clock itself (acquired by the synchronized(this) statement on line 175) is released by wait() when the thread is suspended on line 185 -- that's just the way wait() works. Consequently, the fact that restart() is synchronized is irrelevant. (In fact, it has to be synchronized to call notify().)

The final Clock-related thing to point out is the test on line 196. This test is necessary because of the following scenario.

  1. The clock expires, gets as far as line 194, and is preempted.

  2. Another thread comes along and stops the clock. This in itself is harmless because the stop() method (Listing 4, line 78) just sets the clock reference to null and interrupts the Clock. Neither operation will cause difficulty.

  3. Another thread comes along and sends the Clock a start() message (line 59), which creates a new clock and starts it up.

  4. The original Clock thread now wakes up and executes line 197, setting the newly minted Clock reference to null.

The if statement on line 196 solves the delete-an-active-Clock problem by assuring that a given Clock object can remove only a reference to itself.

Hey you! Time to wake up!

The job of the second thread (Notifier on line 125) is to notify the listeners when the Alarm goes off. This thread is created when a listener is added to addActionListener() on line 110. The thread shuts down automatically when there are no observers to notify. (The while loop on line 131 uses observers != null as its termination condition.)

Related:
1 2 3 4 5 Page 3
Page 3 of 5