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.