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 loopA more practical solution for implementing delays or timed loops is to create a new thread that sleeps before executing its task. Using the
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();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 usingThread.sleep
, as shown in Listing 11-6. As you can see, theinvokeLater
method is used to ensure that thedraw
anderase
methods execute on the event-dispatching thread.
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.