|
Using
Timers in Swing Applications
By Hans Muller
and Kathy Walrath
Programs -- especially those that present GUIs -- often need to
perform a task either repeatedly or after a delay (or both). Timers
make it easy for you to schedule the task. As of 1.3, the Java
platform provides two Timer classes -- one in the javax.swing
package and the other in java.util.
This article describes how to choose and use the appropriate Timer
class for your Swing application. We discuss the problems that timers
solve, give guidelines for choosing the right solution, and provide
examples using timers.
What Timers Do
Generally speaking, a timer supports executing some task periodically
or just once at some future time. Timers are important, albeit specialized,
tools for the GUI programmer because they simplify the job of scheduling
activity that results in a screen update. GUI applications typically
use timers for animation, such as for blinking a cursor, or for
timing responses, such as popping up a tool tip when the mouse is
still for a few moments.
Nearly every computer platform has a timer facility of some kind.
For example, UNIX programs can use the alarm function
to schedule a SIGALRM signal; a signal handler can
then perform the task. The Win32 API has functions such as SetTimer
that let you schedule and manage timer callbacks. The Java platform's
timer facility includes the same basic functionality as other platforms,
and it's relatively easy to configure and extend.
Code Without Timers
In programs written without the benefit of timers, you'll see
some rather nasty code for providing delays or periodic task execution.
The nastiest algorithm of all is the busy wait loop. This little
embarassment attempts to create a delay by keeping the CPU busy:
//DON'T DO THIS!
while (isCursorBlinking()) {
drawCursor();
for (int i = 0; i < 300000; i++) {
Math.sqrt((double)i); // this should really chew up some time
}
eraseCursor();
for (int i = 0; i < 300000; i++) {
Math.sqrt((double)i); // likewise
}
}
|
The reasons why busy wait loops are a bad idea are legion and
obvious, so we won't enumerate them here.
A common, relatively practical approach to creating delays or
timed loops is to create a new thread that sleeps before executing
its task. Using the Thread
sleep method to time the delay works well with Swing
components as long as you follow the rules for thread usage outlined
in the article Threads
and Swing. The previous example could be sensibly rewritten
like this:
final Runnable doUpdateCursor = new Runnable() {
boolean shouldDraw = false;
public void run() {
if (shouldDraw = !shouldDraw) {
drawCursor();
} else {
eraseCursor();
}
}
};
Runnable doBlinkCursor = new Runnable() {
public void run() {
while (isCursorBlinking()) {
try {
EventQueue.invokeLater(doUpdateCursor);
Thread.sleep(300);
}
catch (InterruptedException e) {
return;
}
}
}
};
new Thread(doBlinkCursor).start();
|
As you can see, we've used the invokeLater method to
ensure that the draw and erase methods execute
on the event-dispatching thread. The main problem with this approach
is that it doesn't scale well. Threads and thread scheduling aren't
free or even as cheap as one might hope, so in a system where there
may be many busy threads it's unwise to allocate a thread for every
delay or timing loop.
The Swing Timer Class
The javax.swing.Timer class allows you to schedule
an arbitrary number of periodic or delayed actions with just one
thread. This Timer class is used by Swing components
for things like blinking the text cursor and for timing tool-tip
appearances and disappearances.
The Swing timer implementation fires an action event whenever
the specified interval or delay time passes. You need to provide
an Action
object to the timer. Implement the Action's actionPerformed
method to perform the desired task. For example, the blinking cursor
example above could be written like this:
Action updateCursorAction = new AbstractAction() {
boolean shouldDraw = false;
public void actionPerformed(ActionEvent e) {
if (shouldDraw = !shouldDraw) {
drawCursor();
} else {
eraseCursor();
}
}
};
new Timer(300, updateCursorAction).start();
|
In this example we're using a timer to blink the cursor every
300 milliseconds.
The important difference between using the Swing Timer
class and creating your own Thread is that the Swing
Timer class uses just one thread for all timers. It
deals with scheduling actions and putting its thread to sleep internally
in a way that scales to large numbers of timers. The other important
feature of this timer class is that the Action's actionPerformed
method runs on the event dispatching thread. As a result, you don't
have to bother with an explicit invokeLater call.
The Utility Timer and TimerTask Classes
Timers aren't the exclusive domain of GUI applications. In version
1.3 of the Java platform, support for timers was added to the java.util
package. Like the Swing timer class, the main java.util
timer class is called Timer. (We'll call it the "utility
Timer class" to differentiate from the Swing Timer
class.) Instead of scheduling Actions, the utility
Timer class schedules instances of a class called TimerTask.
The utility timer facility has a different division of labor from
the Swing version. For example, you control the utility timer facility
by invoking methods on TimerTask rather than on Timer.
Still, both timer facilities have the same basic support for delayed
and periodic execution.
The utility timer facility provides more flexibility over scheduling
timers. For example, the utility timer lets you can specify whether
a timer task is to run at a fixed rate or repeatedly after a fixed
delay. The latter scheme, which is the only one supported by Swing
timers, means that a timer's frequency can drift because of extra
delays introduced by the garbage collector or by long-running timer
tasks. This drift is acceptable for animations or auto-repeating
a keyboard key, but it's not appropriate for driving a clock or
in situations where multiple timers must effectively be kept in
lockstep.
The most important difference between javax.Swing.Timer
and java.util.Timer is that the latter doesn't run
its tasks on the event-dispatching thread.
Here's one final version of the blinking cursor example. This
one uses the new javax.util.Timer class.
final Runnable doUpdateCursor = new Runnable() {
private boolean shouldDraw = false;
public void run() {
if (shouldDraw = !shouldDraw) {
drawCursor();
}
else {
eraseCursor();
}
}
};
TimerTask updateCursorTask = new TimerTask() {
public void run() {
EventQueue.invokeLater(doUpdateCursor);
}
};
myGlobalTimer.schedule(updateCursorTask, 0, 300);
|
One other important difference to note here is that each java.util.Timer
instance, such as myGlobalTimer in the previous example,
corresponds to a single thread. It's up to applications to manage
one or more Timer objects.
How to Choose a Timer Class
As we've seen, the Swing and utility timer facilities provide
roughly the same functionality. Generally speaking, we recommend
that you use the utility classes if you're writing a self contained
application, particularly one that's not GUI related. The Swing
Timer class is preferred if you're building a new Swing
component or module that doesn't require large numbers of timers
(where "large" means dozens or more).
The new utility timer classes give you control over how many timer
threads are created; each java.util.Timer object creates
one thread. If your application requires large numbers of timers
you might want to create several java.util.Timer objects
and have each one schedule related TimerTasks. In a
typical application you'll share just one global Timer
object, for which you'll need to create one statically scoped Timer
field or property.
The Swing Timer class uses a single private thread
to schedule timers. A typical GUI component or application uses
at most a handful of timers to control various animation and popup
effects. The single thread is more than sufficient for this.
The other important difference between the two facilities is that
Swing timers run their task on the event dispatching thread, while
utility timers do not. You can hide this difference with a TimerTask
subclass that takes care of calling invokeLater. Here
is an example:
abstract class SwingTimerTask extends java.util.TimerTask {
public abstract void doRun();
public void run() {
if (!EventQueue.isDispatchThread()) {
EventQueue.invokeLater(this);
} else {
doRun();
}
}
}
|
To implement the task, you would then create a SwingTimerTask
subclass and override its doRun method (instead of
run).
An Example
Here's an example that demonstrates an interesting way to use
timers. This example uses the new utility Timer and
TimerTask classes, even though the Swing Timer
class would have worked just as well, because you can already find
good examples of using the Swing Timer class.
To look at the source, or download the demo, here are the necessary
files:
Most of the code is fairly self-explanatory , so we'll just highlight
a few key points. In this example a TimerTask is used
to schedule repaints about as quickly as the application can handle
them. The paintComponent method that handles the fade
operation (see ImagePanel.paintComponent in the example
sources) computes how far along the fade is supposed to be based
on the current time, and then paints accordingly.
The code that estimates that maximum frame rate is important because
the performance of machines running the Java platform can easily
vary by a factor of 10. Here's the method that does this:
public long computeFrameRate() {
Graphics g = createImage(imageWidth, imageHeight).getGraphics();
long dt = 0;
paintComponent(g);
for (int i = 0; i < 10; i++) {
long startTime = System.currentTimeMillis();
paintComponent(g);
dt += System.currentTimeMillis() - startTime;
}
return (long)((float)(dt / 10) * 1.1f);
}
|
We're just measuring the average time the main paintComponent
method needs to draw the display on an offscreen image, plus 10%.
This value will become the rate for the TimerTask that
drives the fade animation. Note that we're stuck with a heuristic
- running the loop 10 times - which reduces the effect of HotSpot
style compilation. We also run the paintComponent method
once before timing it, to avoid timing the class loader. To keep
the example simple, the timing loop runs the first time the user
presses the Fade button. In a real application, using a worker thread
to compute the frame rate when the application started would be
preferable. For more information, see the article Using
a Swing Worker Thread.
Summary
This section introduced you to the Swing and utility timer facilities.
For further information and examples of using Swing timers, see
The Java Tutorial section How
to Use Timers. Also see the API documentation for javax.swing.Timer.
The utility timer facilities are described in the API documentation
for java.util.Timer
and java.util.TimerTask.
Last modified: Fri Mar 17 08:52:26 PST 2000 |