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 4
Page 4 of 5

Getting the synchronization right is tricky here, too. The notification loop on line 131 must synchronize on the outer-class object (Alarm.this) in order to wait on that object. You have no idea how long it's going to take to get through the notifications, however, and you don't want to lock the Alarm during this period. Fortunately, the AWTEventMulticaster (which will be the subject of a future column) is designed with this problem in mind, and it can be used safely to notify listeners without locking the notifying object.

The test on line 142 is essential, because the observers might have been removed while we were waiting. It's possible, though, for run() to be preempted between lines 142 and 143. If observers itself was used in the test rather than the copy I made on line 138, the preempting thread could remove all the Notifier objects, setting the observers reference to null. The subsequent call to actionPerformed() would then throw a NullPointerException.

The main problem with my approach is that the thread could be preempted after the copy was made. The preempting thread could then remove listeners that will, nonetheless, be notified that the alarm goes off. This is a generic problem with all AWT listeners, by the way. If notifications are in progress when a listener is removed, it's still possible for that listener to be notified. You have to plan for this eventuality when you write the code. Observers added after the notifications are started are also not notified, but that's as it should be.

The next problem is creating the thread. The test in addActionListener() starts up a new notifier thread if one doesn't exist, but as usual, things are not as simple as they first appear. There are two problems: you can end up with too many Notifier objects or you can end up with no Notifier objects at all. The first problem is easily solved by synchronizing addActionListener(). Since only one thread at a time can be in the method, we don't have to worry about being preempted after the test but before the new. The second problem can occur when we're killing the thread. The simple strategy, of setting notifier to null as run() exits, doesn't work because run() could be preempted while notifications are in progress but before notifier is set to null. The preempting thread would assume a notifier exists (because we haven't set it to null yet), and not create a new notifier. Control would then transfer back to the notifier thread, which would then kill itself and set notifier null. The net effect is that no notification thread would exist.

I've solved the problem by setting notifier to null on line 139, while the notifier is still synchronized with the Alarm. Now if somebody comes along and adds an observer while notifications are in progress, we'll end up (briefly) with two threads. But the original one will safely go out of existence when it's done with the current batch of notifications.

Initialization is also tricky. The addActionListener() method must first add the notifier, then create the thread. Otherwise the test on line 131 will fail immediately. The addActionListener() method must also be synchronized so that we aren't interrupted after creating the observer but in the middle of initialization. Also, we have to make a decision about whether or not to synchronize removeActionListener(). Consider the following scenario:

  1. addActionListener() is called and starts up the thread. run() is preempted just before entering the while loop.

  2. removeActionListener() (unsynchronized in this scenario) is called and removes a listener. It is preempted as AwtEventMulticaster.remove() is returning, after the listener has been removed but before the control is passed back to the caller. AwtEventMulticaster.remove() is thread-safe, so we don't have to worry about it, at least.

  3. run() reactivates, and the while loop is executed because observers has not been modified yet, even though there are no observers.

At this juncture, the notification thread exists, but it will harmlessly do nothing (and terminate itself) when the Alarm goes off. If removeActionListener() had been synchronized, the superfluous notifier thread would still be created, but this time we'd get all the way through to the synchronized statement that guards the wait() on line 134 before blocking. The listener would now be removed, and we're back where we were before.

Given that there's no advantage to synchronization, I decided not to synchronize removeActionListener(). No point in incurring the extra overhead unnecessarily.

This is not a crash-and-burn sort of failure, because eventually the timer will go off, no Notifier objects will be found, and the thread will terminate. Nonetheless, I don't like threads to be kicking around unnecessarily.

Listing 4. (/src/com/holub/asynch/Alarm.java) Roll Your Own Timer
001  
002  
003  
004  
005  
006  
007  
008  
009  
010  
011  
012  
013  
014  
015  
016  
017  
018  
019  
//-------------------------------------------------------
// This code (c) 1998 Allen I. Holub. All Rights Reserved.
//-------------------------------------------------------
// This code may not be distributed by yourself except in binary form,
// incorporated into a java .class file. You may use this code freely
// for personal purposes, but you may not incorporate it into any
// commercial product without express permission of Allen I. Holub in writing.
//
// Sources code for this class can be found in the "Goodies" section of
// <http://www.holub.com>.
//-------------------------------------------------------
package com.holub.asynch;
import java.util.*;         // Need Date for testing
import java.awt.event.*;    // For ActionListener, etc.
import java.awt.*;          // For ActionListener, etc.

/**
|    An interval timer. Once you "start" it, it runs for a predetermined amount of time. The timer runs on its own relatively high-priority thread while one or more other threads block, waiting for the timer to "expire." Three sorts of timers are supported:
A "one-shot" timer
runs once, then expires. May be started again, but generally isn't.
A "multi-shot" timer
works like a "one-shot" but expects to be started again so is more efficient in this situation. Must be stopped explicitly.
A "continuous" (oscillator-style) timer
runs continuously. Starts up again automatically when it expires. Must be stopped explicitly.
A ll timers may be restarted (have the time interval set back to the original value) while they are running. Warnings:
  1. It's easy to forget to stop() a multi-shot or continuous timer, and neglecting to do so can create a memory leak as a consequence. (The VM will keep the Thread object alive but suspended, even if there are no external references to it.) A finalizer is provided to throw an exception in this situation, but since the finalizer may not be called, that's not a perfect solution.
  2. Calling wait() on a timer is possible, but it only works correctly for CONTINUOUS timers. The await() method works correctly for all types of timers, and is easier to use since you don't have to synchronize first. The only time that using a raw wait() makes sense if if you're interested in the InterruptedException, which is silently absorbed by await().
  **/

    /****************************************************************
    |    CONTINUOUS alarms run continuosly (until stopped), sending out notification messages and releasing waiting threads at a regular interval.
     */

    /****************************************************************
    |    ONE_SHOT alarms run once, then stop automatically. [You do not have to call stop().] Can be started again manually by calling start(), but a new internal thread must be created to do so.
     */

    /****************************************************************
    |    MULTI_SHOT alarms work just like ONE_SHOT alarms, but they can be restarted more efficiently. (The timer thread for a MULTI_SHOT alarm is not destroyed until you stop() the alarm.) Use a MULTI_SHOT when you know that the one-shot will be restarted at regular intervals and don't mind having an extra thread kicking around in a suspened state.
     */

033  
034  
035  
036  
037  
038  
039  
040  
041  
  public static final Mode MULTI_SHOT  = new Mode();
   private Mode type;
    //---------------------------------------------------
   private ActionListener  observers   = null;
   private Thread          notifier;

    /****************************************************************
    |    "Action command" sent to the ActionListeners when the timer has been stopped manually.
     */

    /****************************************************************
    |    "Action command" sent to the ActionListeners when the timer has expired or "ticked."
     */

    /****************************************************************
    |    

Create a timer that expires after the indicated delay in milliseconds. Generally, timers must run at high priority to prevent the waiting threads from starting up immediately on being notified, thereby messing up the timing. If a high-priority thread is waiting on a recurring timer, however, the next "tick" could be delayed by the amount of time the high-priority thread spends doing whatever it does after being notified. Alarms run at the highest priority permitted by the thread group of which they are a member, but spend most of their time sleeping.

@param delay Time to expiration (nominal) in milliseconds.

@param type

One of the following symbolic constants (see):

CONTINUOUSThe timer runs continuously Must call stop() when you're done with it.
ONE_SHOTThe timer runs once, then stops automatically. You do not have to call stop().
MULTI_SHOTLike ONE_SHOT, but can be restarted more efficiently.
If this argument is null, CONTINUOUS is used.
     **/

047  
048  
049  
050  
051  
052  
  public Alarm( int delay, Mode type )
    {   this.delay  = delay;
        this.type   = (type == null) ? CONTINUOUS : type ;
    }

    /****************************************************************
    |    Make a continuous timer.
     */

053  
054  
055  
056  
057  
    public Alarm( int delay )
    {   this( delay, CONTINUOUS );
    }

    /****************************************************************
    |    Start up a timer or restart an expired timer. If the timer is running, it is set back to the original count without releasing any of the threads that are waiting for it to expire. (For example, if you start up a 10-second timer and then restart it after 5 seconds, the waiting threads won't be notified until 10 seconds after the restart---15 seconds after the original start time.) Starting a running timer causes a new thread to be created.
     **/

058  
059  
060  
061  
062  
063  
064  
065  
066  
067  
068  
069  
070  
071  
072  
073  
074  
075  
076  
  public synchronized void start()
    {   if( clock != null )
        {   if( type == MULTI_SHOT && clock.has_expired() )
            {
                clock.restart();
                return;
            }
            else
            {   clock.no_notifications();
                clock.interrupt();
            }
        }
        clock = new Clock();
        clock.start();
        is_stopped = false;
    }

    /****************************************************************
    |    Stops the current timer abruptly, releasing all waiting threads. The timer can be restarted by calling start(). There's no good way for a thread to determine if it was notified as the result of a stop() or a normal expiration.
     **/

077  
078  
079  
080  
081  
082  
083  
084  
085  
086  
  public synchronized void stop()
    {   if( clock != null ) 
            clock.interrupt();
        clock      = null;
        is_stopped = true;
        notifyAll();
    }

    /****************************************************************
    |    It is a bug not to stop() a CONTINUOUS or MULTI_SHOT timer when you're done with it. This finalizer helps you detect the bug by throwing an Error() if the timer that's being destroyed is still running.
     **/

087  
088  
089  
090  
091  
092  
  public void finalize()
    {   if( clock != null )
            throw new Error("Alarm was not stopped before being destroyed");
    }

    /****************************************************************
    |    A long time (roughly 292,271,023 years) that you can use for the timeout value in await():{@link await()}}.`
     **/

    /****************************************************************
    |    

Wait for the timer to expire. Returns immediately in the case of an expired ONE_SHOT or MULTI_SHOT timer. Blocks until the timer expires in all other situations (including any sort of timer that has not yet been started).

@return false if the method returned because the timer was stopped, true if the timer simply expired.

@see FOREVER

     **/

096  
097  
098  
099  
100  
101  
102  
103  
104  
105  
  public synchronized boolean await( long timeout )
    {
        if( clock == null || !clock.has_expired() )
        {   try                             { wait( timeout );  }
            catch( InterruptedException e ) { /*do nothing*/    }
        }
        return !is_stopped;
    }
1 2 3 4 5 Page 4
Page 4 of 5