|
|
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
Page 5 of 5
The strategy I eventually hit on is to test a class using an inner class of the class I'm testing. I always call this class
Test. This inner class resides in its own class file, and I don't ship it with the application, so it doesn't increase the application's
size. The test class's name is within the name space of the outer class, so the global name space isn't corrupted with stuff
the user of the class couldn't care less about. Given a set of classes listed in a makefile, I can easily run a whole battery
of tests from a makefile by deriving the test class name from the source file name using macros. For example, I can test the
current class by executing:
java Animator1\$Test
(You don't need the backslash on Windows machines.) You can't write java Animator.Test because the VM will think you're trying to run the Test class in the Animator package.
Use Timer
Getting back to animations, Swing solves the it's-not-safe-to-call-a-Swing-method-from-another-thread problem with the Timer class, which executes requests on Swing's own event processing thread at fixed intervals. I think of this kind of timer as
"proactive." You set up a timer interval and register one or more ActionListener objects with it. The Timer then sends an actionPerformed() message to each listener when the interval elapses. (All the listeners are notified at once, not one per interval.) These
notifications are executed from the Swing event loop, so they can happily call Swing methods without worrying about thread
synchronization.
Listing 2 below demonstrates how to use a timer by printing the time once per second. The Timer is initialized with a time interval (1000 milliseconds) and is provided an ActionListener to notify when the interval expires. The listener's actionPerformed() method both prints the current time and advances the elapsed-time counter. After five seconds (and five calls to actionPerformed()), the condition variable (which I discussed last month) is set to true, thereby releasing any threads that are waiting for the timer to finish. The main thread creates a Swing_timer, the constructor of which starts up the clock, then blocks, waiting for the done condition to become true. The net effect is that the constructor doesn't return until five seconds have elapsed.
Sun's Timer is a bit more flexible than I've shown; it can also work in one-shot mode, where the listeners are notified only once, for
example. The Timer also supports methods that let you add additional listeners, change the timer interval, and so forth.
|
So, how does it work?
It's interesting to delve into the inner workings of Swing's Timer. (The sources are in the JDK's src.jar file, installed in the JDK root directory if you tell the installer program to install
it.)
You would expect a timer to fire off a thread whose run() method would first wait for the time interval to elapse, then notify listeners. This thread would then either terminate or
loop back up and wait again, depending on the timer's mode. That's more or less how my Alarm class (which I'll present in a moment) works, and that's how our failed attempt at an animation loop worked as well.
Swing's Timer doesn't work this way, however. What you don't see on the surface is a second package-access class, called a TimerQueue. This class is a Singleton -- only one instance of it exists. It's the TimerQueue object that encapsulates the "wait" thread. The Timer object actually doesn't do any timing at all. It just stores its time interval internally. When you start it up, the Timer object adds itself to the TimerQueue. The queue keeps the timers ordered by expiration time, and puts itself to sleep until that time arrives. The TimerQueue object then passes the ball back to the expired timer, asking it to notify its listeners.
At this juncture, the Timer object calls Swing's invokeLater() method (discussed earlier) and passes it a Runnable object whose run() method sends ActionPerformed() messages to the timer's ActionListener objects. It's important to observe that this notification happens on Swing's event processing thread, so the ActionPerformed() method can happily call Swing functions without worrying about thread safety.
By default, if several references to the same timer object are waiting in the notification queue, all but one are ignored. (Notification requests can build up in this
way if the application is very busy servicing normal OS-event handlers.) This way, a listener doesn't get multiple notifications
coming one on top of the other. This behavior can be suppressed by passing the Timer object a setCoalesce(false) message.
In Listing 3, below, I've rewritten the earlier animation example to use a Swing Timer. The new code is in boldface, and the main difference between this version and the previous one is the replacement of the
run() method with an actionPerformed(), which is invoked from the Swing event thread when the timer expires.
|
Why use a Swing Timer?
The main advantage to Swing's approach (over and above the thread-safety issue) is that the number of threads that exist at
a given moment is small, since all the real work is done on the Swing event thread, and all timers share the same TimerQueue.
There are lots of disadvantages, though:
Timer.ActionPerfomed() method to be called.ActionPerformed() executes on the event thread, the UI is locked up while the ActionPerformed() method is executing. It's a bad idea to lock up the UI by doing something unrelated to the UI.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.
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.
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.
stop() method (Listing 4, line 78) just sets the clock reference to null and interrupts the Clock. Neither operation will cause difficulty.Clock a start() message (line 59), which creates a new clock and starts it up.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.)
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:
addActionListener() is called and starts up the thread. run() is preempted just before entering the while loop.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.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.
|
|||||
/**
**/ |
020 021 022 023 024 025 026 027 028 |
public class Alarm
{ // A compiler bug (1.1x, 1.2rc1)
private int delay; // permit blank finals.
private Alarm.Clock clock = null;
private boolean is_stopped = false;
public static class Mode { private Mode(){} }
|
/****************************************************************
*/ |
029 030 |
/****************************************************************
*/ |
031 032 |
/****************************************************************
*/ |
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; |
/****************************************************************
*/ |
042 043 |
/****************************************************************
*/ |
044 045 046 |
/****************************************************************
**/ |
047 048 049 050 051 052 |
public Alarm( int delay, Mode type ) { this.delay = delay; this.type = (type == null) ? CONTINUOUS : type ; } |
/****************************************************************
*/ |
053 054 055 056 057 |
public Alarm( int delay )
{ this( delay, CONTINUOUS );
}
|
/****************************************************************
**/ |
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; } |
/****************************************************************
**/ |
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(); } |
/****************************************************************
**/ |
087 088 089 090 091 092 |
public void finalize() { if( clock != null ) throw new Error("Alarm was not stopped before being destroyed"); } |
/****************************************************************
**/ |
093 094 095 |
/****************************************************************
**/ |
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; } |
/****************************************************************
**/ |
106 107 108 |
public boolean await(){ return await( FOREVER ); }
|
/****************************************************************
*/ |
109 110 111 112 113 114 115 116 117 118 |
public synchronized void addActionListener(ActionListener l) { observers = AWTEventMulticaster.add(observers, l); if( notifier == null ) { notifier = new Alarm.Notifier(); notifier.setDaemon( true ); notifier.start(); } } |
/****************************************************************
*/ |
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
public void removeActionListener(ActionListener l) { observers = AWTEventMulticaster.remove(observers, l); } private class Notifier extends Thread { public void run() { ActionListener copy; while( observers != null ) { synchronized( Alarm.this ) { try { Alarm.this.wait(); } catch(InterruptedException e){/*ignore*/} if( (copy = observers) == null) notifier = null; } if( copy != null ) copy.actionPerformed( new ActionEvent(this,0, is_stopped ? STOPPED : EXPIRED)); } // can't set notifier=null here } } //================================================================ // Support classes: //================================================================ private final class Clock extends Thread { private boolean expired = false; // continuous timers don't expire private boolean notifications_off = false; Clock() { setPriority( getThreadGroup().getMaxPriority() ); } public void run() { while( !isInterrupted() ) { try { sleep(delay); // release the monitor while sleeping if( isInterrupted() ) // don't notify waiting threads if break; // we've been stopped by the // Alarm object. synchronized( this ) { expired = true; if( !notifications_off ) { synchronized( Alarm.this ) { Alarm.this.notifyAll(); } } if( type == MULTI_SHOT ) { wait(); // suspend } else if( type == ONE_SHOT ) { // Set the outer-class reference to the // current clock to null so that the // memory can be reclaimed. Note that // we're still synchronized on the outer- // class object. synchronized( Alarm.this) { if( Alarm.this.clock == this ) Alarm.this.clock = null; } break; } } } catch(InterruptedException e) // don't notify the waiting { break; // threads because an } // interrupt is used to stop } // the timer. } public void no_notifications() { notifications_off = true; } public synchronized void restart() { expired = false; notify(); // resume } public boolean has_expired() // CONTINUOUS { return (type != CONTINUOUS) && expired; // timers never } // expire. }; //================================================================ // Unit test: //================================================================ static public class Test { public static void main( String[] args ) throws Exception { // A recurring timer, runs until it is stoped manually. Alarm clock = new Alarm(1000, Alarm.CONTINUOUS ); clock.start(); System.out.println("Print time 3 times at 1-second intervals"); for( int i = 3; --i>= 0; ) { System.out.println( new Date().toString() ); clock.await( Alarm.FOREVER ); } clock.stop(); // It is essential to stop the timer manually. // Otherwise, the memory for it might never be // reclaimed. System.out.println("\nOne-shot:\n"); // A One-shot. Fire it manually. You don't need to stop() // it explicitly---it automatically frees up all threads // when it expires. clock = new Alarm(1000, Alarm.ONE_SHOT); clock.start(); for( int i = 3; --i>= 0; ) { System.out.println( new Date().toString() + "\r" ); clock.await( Alarm.FOREVER ); clock.start(); } System.out.println("\n Multi-shot:\n"); // A Multi-shot is much like a one-shot. Fire it manually, // but you must stop() it explicitly. The main difference // is that a MULTI_SHOT timer doesn't recreate the timer // thread when it's restarted. The one-shot timer creates // the thread anew every time it's started. clock = new Alarm(1000, Alarm.MULTI_SHOT); clock.start(); for( int i = 3; --i>= 0; ) { System.out.println( new Date().toString() + "\r" ); clock.await( Alarm.FOREVER ); clock.start(); } clock.stop(); System.out.println( "\n Notifier\n" ); clock = new Alarm( 1000 ); // 1-second continuous timer clock.addActionListener ( new ActionListener() { public void actionPerformed( ActionEvent e ) { System.out.println( new Date().toString() + e.getActionCommand() ); } } ); clock.start(); System.out.println("Sleeping"); Thread.currentThread().sleep( 5000 ); System.out.println("Waking"); clock.stop(); System.exit(0); } } } |
Frankly, I'm not completely convinced that all this will indeed work correctly in all situations. I've stared at the code, made diagrams, and analyzed it up the wazoo, but if past experience is any indication, I've probably still missed something. Welcome to the world of thread programming. If you find a flaw in my reasoning (and some of you probably will -- sigh), please send me e-mail.
So that's it for this month. A Timer is a useful thing to have around, not just for animation loops, but for any application that needs to do things at regular
intervals or during idle time. Between the new Swing Timer and my own Alarm, all the applications I can think of are covered. The synchronization issues in this code are hairier than usual, but it
does serve as a good example of threading in the real world.
Next month, I'll continue with even more threading stuff by delving into implementations of the Observer and Singleton design
patterns that work in multithreaded environments. I'll look at the inner workings of the AWTEventMulticaster class and other ways of sending notifications efficiently in a thread-safe way. I'll also look at how to create a Singleton
efficiently, and how to work around a 1.1 VM bug that makes it difficult to produce Singletons.
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.
Timer class in the JDK docs and at http://java.sun.com/products/jdk/1.2/docs/api/javax/swing/Timer.html