This chapter provides a set of design guidelines and techniques you can use to ensure that your Swing GUIs perform well and provide fast, sensible responses to user input. Many of these guidelines imply the need for threads, and sections 11.2, 11.3, and 11.4 review the rules for using threads in Swing GUIs and the situations that warrant them. Section 11.5 describes a web-search application that illustrates how to apply these guidelines.
Note: Sections 11.2 and 11.3 are adapted from articles that were originally published on The Swing Connection. For more information about programming with Swing, visit The Swing Connection online at http://java.sun.com/products/jfc/tsc/.
Following these guidelines should mitigate or eliminate GUI responsiveness problems. However, you need to consider the specific recommendations that follow in the context of your own program and apply the ones that make sense.
Design work tends to be time-consuming and expensive, while building software that implements a good design is relatively easy. To economize on the design part of the process, you need to have a good feel for how much refinement is really necessary.
For example, if you want to build a small program that displays the results of a simple fixed database query as a graph and in tabular form, there's probably no point in spending a week working out the best threading and painting strategies. To make this sort of judgment, you need to understand your program's scope and have a feel for how much it pushes the limits of the underlying technology.
To build a responsive GUI, you'll generally need to spend a little more time on certain aspects of your design:
The following sections describe four key guidelines for keeping your distributed applications in check:
In a distributed application, it's often not possible to provide results instantaneously. However, a well-designed GUI acknowledges the user's input immediately and shows results incrementally whenever possible.
Your interface should never be unresponsive to user input. Users should always be able to interrupt time-consuming tasks and get immediate feedback from the GUI.
Interrupting pending tasks safely and quickly can be a challenging design problem. In distributed systems, aborting a complex task can sometimes be as time-consuming as completing the task. In these cases, it's better to let the task complete and discard the results. The important thing is to immediately return the GUI to the state it was in before the task was started. If necessary, the program can continue the cleanup process in the background.
One way to do this is to use explicit notifications. For example, if the information is part of the state of an Enterprise JavaBeans component, the program might add property change listeners for each of the properties being displayed. When one of the properties is changed, the program receives a notification and triggers a GUI update. However, this approach has scalability issues: You might receive more notifications than can be processed efficiently.
To avoid having to handle too many updates, you can insert a notification concentrator object between the GUI and the bean. The concentrator limits the number of updates that are actually sent to the GUI to one every 100 milliseconds or more. Another solution is to explicitly poll the state periodically-for example, once every 100 milliseconds.
Imagine a program that displays the results of a database query each time the user presses a button. If the results don't change, the user might think that the program isn't working correctly. Although the program isn't idle (it is in fact performing the query), it looks idle. To fix this problem, you could display a status bar that contains the latest query and the time it was submitted, or display a transient highlight over the fields that are being updated even if the values don't change.
Event processing in Swing is effectively single-threaded, so you don't have to be well-versed in writing threaded applications to write basic Swing programs. The following sections describe the three rules you need to keep in mind when using threads in Swing:
repaint and revalidate
methods on JComponent.) invokeLater and invokeAndWait for doing work
if you need to access the GUI from outside event-handling or drawing code.
SwingWorker or Timer. For example, you might want to
create a thread to handle a job that's computationally expensive or I/O bound.
Realized means that the component's
paint method has been or might be called. A Swing component that's
a top-level window is realized by having setVisible(true),
show, or (this might surprise you) pack called on it.
Once a window is realized, all of the components that it contains are realized.
Another way to realize a Component is to add it to a
Container that's already realized.
The event-dispatching thread is the thread that
executes the drawing and event-handling code. For example, the
paint and actionPerformed methods are automatically
executed in the event-dispatching thread. Another way to execute code in the
event-dispatching thread is to use the AWT EventQueue.invokeLater
method.
There are a few exceptions to the single-thread rule:
init
method. Existing browsers don't render an applet until after its
init and start methods have been called, so
constructing the GUI in the applet's init method is safe as long
as you never call show or setVisible(true) on the
actual applet object. JComponent methods can be called from any thread:
repaint, revalidate, and invalidate.
The repaint and revalidate methods queue requests
for the event-dispatching thread to call paint and
validate, respectively. The invalidate method just
marks a component and all of its direct ancestors as requiring validation.
add<ListenerType>Listener and
remove<ListenerType>Listener methods.
These operations have no effect on event dispatches that might be under way.
main thread. For
example, the code in Listing
11-1 is safe, as long as no Component objects (Swing or
otherwise) have been realized.
public class MyApplication {
public static void main(String[] args) {
JPanel mainAppPanel = new JPanel();
JFrame f = new JFrame("MyApplication");
f.getContentPane().add(mainAppPanel,
BorderLayout.CENTER);
f.pack();
f.setVisible(true);
// No more GUI work here
}
}
Constructing a GUI in the main
thread
In this example, the f.pack call realizes
the components in the JFrame. According to the single-thread rule,
the f.setVisible(true) call is unsafe and should be executed in the
event-dispatching thread. However, as long as the program doesn't already have a
visible GUI, it's exceedingly unlikely that the JFrame or its
contents will receive a paint call before
f.setVisible(true) returns. Because there's no GUI code after the
f.setVisible(true) call, all GUI processing moves from the
main thread to the event-dispatching thread, and the preceding code
is thread-safe.
invokeLater and
invokeAndWait methods to cause a Runnable object to be
run on the event-dispatching thread.
These methods were originally provided in the
SwingUtilities class, but are part of the EventQueue
class in the java.awt package in J2SE v. 1.2 and later. The
SwingUtilties methods are now just wrappers for the AWT versions.
invokeLater requests that some code be executed in the
event-dispatching thread. This method returns immediately, without waiting for
the code to execute.
invokeAndWait acts like invokeLater, except that
it waits for the code to execute. Generally, you should use
invokeLater instead. invokeLater from any thread to request the event-dispatching thread
to run certain code. You must put this code in the run method of a
Runnable object and specify the Runnable object as the
argument to invokeLater. The invokeLater method
returns immediately, it doesn't wait for the event- dispatching thread to
execute the code. Listing
11-2 shows how to use invokeLater.
Runnable doWork = new Runnable() {
public void run() {
// do some GUI work here
}
};
SwingUtilities.invokeLater(doWork);
Using invokeLater
invokeAndWait method is just like the invokeLater
method, except that invokeAndWait doesn't return until the
event-dispatching thread has executed the specified code. Whenever possible, you
should use invokeLater instead of invokeAndWait. If
you use invokeAndWait, make sure that the thread that calls
invokeAndWait does not hold any locks that other threads might need
while the invoked code is running. Listing
11-3 shows how to use invokeAndWait.
Listing
11-4 shows how a thread that needs access to GUI state, such as the contents
of a pair of JTextFields, can use invokeAndWait to
access the necessary information.
void showHelloThereDialog() throws Exception {
Runnable doShowModalDialog = new Runnable() {
public void run() {
JOptionPane.showMessageDialog(myMainFrame,
"HelloThere");
}
};
SwingUtilities.invokeAndWait(doShowModalDialog);
}
Using invokeAndWait
void printTextField() throws Exception {
final String[] myStrings = new String[2];
Runnable doGetTextFieldText = new Runnable() {
public void run() {
myStrings[0] = textField0.getText();
myStrings[1] = textField1.getText();
}
};
SwingUtilities.invokeAndWait(doGetTextFieldText);
System.out.println(myStrings[0] + " " + myStrings[1]);
}
Using invokeAndWait to access GUI
state
Remember that you only need to use these methods if you
want to update the GUI from a worker thread that you created. If you haven't
created any threads, then you don't need to use invokeLater or
invokeAndWait.
Timer classes: one in the
javax.swing package and the other in java.util.
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.
//DON'T DO THIS!
while (isCursorBlinking()) {
drawCursor();
for (int i = 0; i < 300000; i++) {
Math.sqrt((double)i); // this should chew up time
}
eraseCursor();
for (int i = 0; i < 300000; i++) {
Math.sqrt((double)i); // likewise
}
}
Busy wait loop
A more practical solution for implementing delays or timed loops is to create a new thread that sleeps before executing its task. Using the Thread sleep method to time a delay works well with Swing components as long as you follow the rules for thread usage outlined in Section 11.4 on page 176. The blinking cursor example could be rewritten using Thread.sleep, as shown in Listing 11-6. As you can see, the invokeLater method is used to ensure that the draw and erase methods execute on the event-dispatching thread.
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();
Using the Thread sleep method
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 might be many busy threads it's unwise to allocate a thread for every delay or timing loop.
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 the display of tool-tips.
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
actionPerformed method to perform the desired task. For example,
the blinking cursor example above could be written as shown in Listing
11-7. In this example, a timer is used to blink the cursor every 300
milliseconds.
Action updateCursorAction = new AbstractAction() {
boolean shouldDraw = false;
public void actionPerformed(ActionEvent e) {
if (shouldDraw = !shouldDraw) {
drawCursor();
} else {
eraseCursor();
}
}
};
new Timer(300, updateCursorAction).start();
Blinking cursor
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 actionPerformed method runs
on the event-dispatching thread. As a result, you don't have to bother with an
explicit invokeLater call.
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 Action objects, 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 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.
The utility timer facility provides more flexibility over scheduling timers. For example, the utility timer lets you 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 blinking cursor example can easily be implemented
using the java.util.Timer class, as shown in Listing
11-8.
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);
Blinking the cursor with
java.util.Timer
An important difference to note when using the utility
Timer class is that each java.util.Timer instance,
such as myGlobalTimer, corresponds to a single thread. It's up to
the program to manage the Timer objects.
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 program 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 program 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 program uses at most a
handful of timers to control various animation and pop-up 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. Listing
11-9 shows a TimerTask subclass, SwingTimerTask,
that does this. To implement the task, you would then subclass
SwingTimerTask and override its doRun method (instead
of run).
abstract class SwingTimerTask extends java.util.TimerTask {
public abstract void doRun();
public void run() {
if (!EventQueue.isDispatchThread()) {
EventQueue.invokeLater(this);
} else {
doRun();
}
}
}
Extending TimerTask
Cross-fade animation
This animation is implemented using the
java.util.Timer and SwingTimerTask classes. The
cross-fade is implemented using the Graphics and Image
classes. Complete code for this sample is available online,1
but this discussion concentrates on how the timers are used.
A SwingTimerTask is used to schedule the
repaints for the animation. The actual fade operation is handled in the
paintComponent method, which computes how far along the fade is
supposed to be based on the current time, and paints accordingly.
The user interface provides a slider that lets the user
control how long the fade takes-the shorter the time, the faster the fade. When
the user clicks the Fade button, the setting from the slider is passed to the
startFade method, shown in Listing
11-10. This method creates an anonymous subclass of
SwingTimerTask (Listing
11-9) that repeatedly calls repaint. When the task has run for
the allotted time, the task cancels itself.
public void startFade(long totalFadeTime) {
SwingTimerTask updatePanTask = new SwingTimerTask() {
public void doRun() {
/* If we've used up the available time then cancel
* the timer.
*/
if ((System.currentTimeMillis()-startTime) >= totalTime) {
endFade();
cancel();
}
repaint();
}
};
totalTime = totalFadeTime;
startTime = System.currentTimeMillis();
timer.schedule(updatePanTask, 0, frameRate);
}
Starting the animation
The last thing the startFade method does is
schedule the task. The schedule method takes three arguments: the
task to be scheduled, the delay before starting, and the number of milliseconds
between calls to the task.
It's usually easy to determine what value to use for the
task delay. For example, if you want the cursor to blink five times every
second, you set the delay to 200 milliseconds. In this case, however, we want to
call repaint as often as possible so that the animation runs smoothly. If
repaint is called too often, though, it's possible to swamp the CPU
and fill the event queue with repaint requests faster than the requests can be
processed. To avoid this problem, we calculate a reasonable frame rate and pass
it to the schedule method as the task delay. This frame rate is
calculated in the initFrameRate method shown in Listing
11-11.
public void initFrameRate() {
Graphics g = createImage(imageWidth,
imageHeight).getGraphics();
long dt = 0;
for (int i = 0; i < 20; i++) {
long startTime = System.currentTimeMillis();
paintComponent(g);
dt += System.currentTimeMillis() - startTime;
}
setFrameRate((long)((float)(dt / 20) * 1.1f));
}
Initializing the frame rate
The frame rate is calculated using the average time that
it takes the paintComponent method to render the component to an
offscreen image. The average time is multiplied by a factor of 1.1 to slow the
frame rate by 10 percent to prevent minor fluctuations in drawing time from
affecting the smoothness of the animation.
For additional information about using Swing timers, see How to Use Timers in The Java Tutorial.2
If it might take a long time or it might block, use a thread. If it can occur later or it should occur periodically, use a timer.
Occasionally, it makes sense to create and start a thread directly; however, it's usually simpler and safer to use a robust thread-based utility class. A thread-based utility class is a more specialized, higher-level abstraction that manages a worker thread. The timer classes described in Section 11.3 are good examples of this type of utility class. Concurrent Programming in Java3 by Doug Lea describes many other useful thread-based abstractions.
Swing provides a simple utility class called
SwingWorker that can be used to perform work on a new thread and
then update the GUI on the event-dispatching thread. SwingWorker is
an abstract class. To use it, override the construct method to
perform the work on a new thread. The SwingWorker
finished method runs on the event-dispatching thread. Typically,
you override finished to update the GUI based on the value produced
by the construct method. (You can read more about the
SwingWorker class on The Swing Connection.4)
The example in Listing
11-12 shows how SwingWorker can be used to check the modified
date of a file on an HTTP server. This is a sensible task to delegate to a
worker thread because it can take a while and usually spends most of its time
blocked on network I/O.
final JLabel label = new JLabel("Working ...");
SwingWorker worker = new SwingWorker() {
public Object construct() {
try {
URL url = new URL("http://java.sun.com/index.html");
return new Date(url.openConnection().getLastModified());
}
catch (Exception e) {
return "";
}
}
public void finished() {
label.setText(get().toString());
}
};
worker.start(); // start the worker thread
Checking the state of a remote file using a worker thread
In this example, the construct method
returns the last-modified date for java.sun.com, or an error string
if something goes wrong. The finished method uses
SwingWorker.get, which returns the value computed by the
construct method, to update the label's text.
Using a worker thread to handle a task like the one in the previous example does keep the event-dispatching thread free to handle user events; however, it doesn't magically transform your computer into a multi-CPU parallel-processing machine. If the task keeps the worker thread moderately busy, it's likely that the thread will absorb cycles that would otherwise be used by the event-dispatching thread and your program's on-screen performance will suffer. There are several ways to mitigate this effect:
The example in the next section illustrates as many of these guidelines and techniques as possible. It's a front end for web search engines that resembles Apple's Sherlock 2 application5 or (to a lesser extent) Infoseek's Express Search application.6
These types of user interfaces push the limit of what works well in the HTML-based, thin-client application model. Many of the operations that you might expect to find in a search program, such as sorting and filtering, can't easily be provided under this dumb-terminal-style application model.
On the other hand, the Java platform is uniquely suited for creating user interfaces for web services like search engines. The combination of networking libraries, HTTP libraries, language-level support for threads, and a comprehensive graphics and GUI toolkit make it possible to quickly create full-featured web-based applications.
Search Party application
The Search Party application, shown in Figure 11-2, provides this kind of Java technology-based user interface for a set of web search engines. It illustrates how to apply the guidelines and techniques described in this chapter to create a responsive GUI. You can download the complete source code for the Search Party application from http://java.sun.com/docs/books/performance/.
Search Party allows the user to enter a simple query that's delivered to a list of popular search engines. The results are collected in a single table that can be sorted, filtered, and searched. The GUI keeps the user up-to-date on the search tasks that are running and lets the user interrupt a search at any time.
Worker threads are used to connect to the search engines and parse their results. Each worker thread delivers updates to the GUI at regular intervals. After collecting a couple hundred search hits, the worker thread exits. If the user interrupts the search, the worker threads are terminated.
The following sections take a closer look at how the worker threads operate.
Thread.MIN_PRIORITY. The thread-priority property allows you to
advise the underlying system about the importance of scheduling the thread. How
the thread- priority property is used depends on the JVM implementation. Some
implementations make rather limited use of the priority property and small
changes in thread priority have little or no effect. In other JVM
implementations, a thread with a low priority might starve (never be scheduled)
if there are always higher-priority threads that are ready to run.
In the Search Party application, the only thread we're concerned about competing with is the event-dispatching thread. Making the worker threads' priorities low is reasonable because we're always willing to suspend the worker threads while the user is interacting with the program.
When the Thread.interrupt method is called,
it just sets the thread's interrupted boolean property. If the interrupted
thread is sleeping or waiting, an InterruptedException is thrown.
If the interrupted thread is blocked on I/O, an
InterruptedIOException might be thrown, but throwing the exception
isn't required by the JVM specification and most implementations don't.
Search Party's SwingWorker subclass,
SearchWorker, checks to see if it's been interrupted each time it
reads a character from the buffered input stream. Although the obvious way to
implement this would be to call Thread.isInterrupted before reading
a character, this approach isn't reliable. The isInterrupted flag
is cleared when an InterruptedException is caught or when the
special "interrupted" test and reset method is called. If some code that we've
implicitly called happens to catch the InterruptedException
(because it was waiting or sleeping) or if it clears the
isInterrupted flag by calling Thread.interrupted,
Search Party wouldn't realize that it's been interrupted! To make sure that
Search Party detects interruptions, the SwingWorker
interrupt method interrupts the worker thread and
permanently sets the boolean
flag that is returned by the SwingWorker method
isInterrupted.
What happens if the interrupted worker thread is blocked
on I/O while waiting for data from the HTTP server it's reading from? It's
unlikely that the I/O code will throw an InterruptedIOException,
which means there's a potential thread leak. To avoid this problem,
SearchWorker class overloads the interrupt method.
When the worker is interrupted, the input stream it's reading from is
immediately closed. This has the nice side effect of immediately aborting any
pending I/O. The SearchWorker implementation catches and ignores
the I/O exception that results from closing the thread's input stream while a
read was pending.
You can download the code for this and other examples from http://java.sun.com/docs/books/performance/.
2Mary Campione and Kathy Walrath, The Java Tutorial: Object-Oriented Programming for the Internet, Second Edition. Addison-Wesley, 1998.
3Doug Lea, Concurrent Programming in Java: Design Principles and Patterns, Second Edition. Addison-Wesley, 1999.
4Visit The Swing Connection online at http://java.sun.com/products/jfc/tsc/.
5For more information about Sherlock, see http://www.apple.com/sherlock/.
6For more information about Express Search, see http://express.go.com/.
Copyright © 2001, Sun Microsystems,Inc.. All rights reserved.