The thread priority in UI application

来源:互联网 发布:晋江网络推广经理招聘 编辑:程序博客网 时间:2024/05/09 15:37
[Contents][Prev] [Next] [Index]

CHAPTER 11 - Writing Responsive User Interfaces with Swing

Much of what has been written about GUI design focuses on the layout of controls, the presentation of data, and the mechanics of completing various tasks. However, even a program that has a great-looking, intuitive interface can be virtuallyunusable if it doesn't respond well to user actions. Performance is a key aspectof GUI design that's often overlooked until it's identified as a problem late in the development cycle.

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 sections11.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, visitThe Swing Connection online at http://java.sun.com/products/jfc/tsc/.

11.1 Guidelines for Responsive GUIs

This section introduces two basic guidelines for writing responsive GUIs:

  • Design, then build.
  • Put the user in charge.
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.

11.1.1 Design, Then Build (Repeat)

In nearly any engineering endeavor, it's important to spend time designing your product before you try to build it. While this is obvious, it's not always as clear how much time you should spend on the design before you start trying to implementit.

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:

  • Managing component lifecycles. If you're working with a large number of GUI components, think carefully about how and when they're created. You also need to consider when to cache, reuse, and discard components.

  • Constructing windows. If your GUI contains more than a handful of optional windows, construct and cache the windows that are most likely to be needed next. For example, in a forms application where a small subset of forms is shown to the user for a particular task, cache the forms that are relevant for that task.

  • Handling timing issues in distributed applications. If your program uses services provided by other machines, or even other processes on the same machine, its GUI must accommodate performance latencies and other unpredictabletiming issues. This is essential to making the program feel responsive to the user.

11.1.2 Put the User in Charge

Today, new programs are often distributed-they depend on services provided by other processes on a network, often the Internet. In this environment, a good performancemodel for a program is taking a dog for a walk: It's OK for the dog to stop and sniff as long as a tug on the leash gets a quick, appropriate response. Your program is the dog and the user is holding its leash. Your performance-engineering job is to make sure that the leash is as short as possible and your programis well-behaved. You don't want your 200-pound Labrador rolling around in the neighbor's geraniums on a 50-foot leash made of rubber bands.

The following sections describe four key guidelines for keeping your distributed applications in check:

  • Don't make the user wait.
  • Let the user know what's going on.
  • Stay in sync.
  • If it looks idle, it should be idle.

Don't Make the User Wait

If the user has to wait for more than 50 milliseconds for a response, the program is going to seem slow. Pauses of less than 50 milliseconds between when the user presses a key or button and when the GUI responds feel instantaneous to the user. As the delay grows, the GUI begins to feel sluggish. When the delay reaches 5 seconds or more, users are likely to assume that the program isn't working at all. In response, they often bash the keyboard and mouse in frustration, which can renderthe program truly nonfunctional.

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.

Let the User Know What's Going On

When the user launches a time-consuming task and has to wait for the results, make it clear what's going on. If possible, give an estimate of how long it will take to complete the task. If you can't provide a reasonable estimate, say so.

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.

Stay in Sync

Distributed applications often display information that's stored on remote servers. You need to make sure that the displayed data stays in sync with the remote data.

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 notificationconcentrator 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.

If It Looks Idle, It Should Be Idle

When a program appears to be idle, it really should be idle. For example, when an application is iconified, it should remove listeners that have been installed on objects in other processes and pause or terminate polling threads. Conversely, if a program is consuming resources, there should be some evidence of that on the screen. This gets back to letting the user know what's going on.

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.

11.2 Using Threads in Swing Programs

The design guidelines presented in Section 11.1 lead to certain implementation decisions, particularly concerning the use of threads. Using threads properly can be the key to creating a responsive user interface with Swing. Because support for threads was built into the Java programming language, using threads is relatively easy; however, using them correctly can be difficult.

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:

  • Swing components can be accessed by only one thread at a time. Generally, this thread is the event-dispatching thread. (A few operations are guaranteed to be thread-safe, notablyrepaint and revalidate methods on JComponent.)

  • Use invokeLater and invokeAndWait for doing work if you need to access the GUI from outside event-handling or drawing code.

  • If you need to create a thread, use a thread utility class such as SwingWorker orTimer. For example, you might want to create a thread to handle a job that's computationally expensive or I/O bound.

11.2.1 The Single-Thread Rule

Once a Swing component has been realized, all code that might affect or depend on the state of that component should be executed in the event-dispatching thread. This might sound scary, but for many simple programs, you don't have to worry about threads at all.

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 havingsetVisible(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 aComponent 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, thepaint 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 AWTEventQueue.invokeLater method.

There are a few exceptions to the single-thread rule:

  • A few methods are thread-safe. In the Swing API documentation, thread-safe methods are tagged with the message: "This method is thread safe, although most Swing methods are not."

  • A program's GUI can often be constructed and displayed in the main thread. (See the next section,Constructing a GUI in the Main Thread, for more information.)

  • An applet's GUI can be constructed and displayed in its init method. Existingbrowsers don't render an applet until after itsinit and start methods have been called, so constructing the GUI in the applet'sinit method is safe as long as you never call show or setVisible(true) on the actual applet object.

  • Three JComponent methods can be called from any thread: repaint,revalidate, and invalidate. The repaint andrevalidate methods queue requests for the event-dispatching thread to callpaint and validate, respectively.The invalidate method just marks a component and all of its direct ancestors as requiring validation.

  • Listener lists can be modified from any thread. It's always safe to call the add<ListenerType>Listener and remove<ListenerType>Listener methods.These operations have no effect on event dispatches that might be under way.

Constructing a GUI in the Main Thread

You can safely construct and display a program's GUI in themain 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 theJFrame. 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 beforef.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.

11.2.2 Using invokeLater and invokeAndWait for Event Dispatching

Most post-initialization GUI work naturally occurs in the event-dispatching thread. Once the GUI is visible, most programs are driven by events such as buttonactions or mouse clicks, which are always handled in the event-dispatching thread. A program that uses separate worker threads to perform GUI-related processingcan useinvokeLater 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 theEventQueue class in the java.awt package in J2SE v. 1.2 and later. TheSwingUtilties 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 useinvokeLater instead.
The two following sections show some examples of how these methods are used.

Using the invokeLater Method

You can call invokeLater from any thread to request the event-dispatching thread to run certain code. You must put this code in therun method of a Runnable object and specify the Runnable object as the argument toinvokeLater. 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

Using the invokeAndWait Method

The 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 useinvokeLater 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 ofJTextFields, 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 useinvokeLater or invokeAndWait.



Why Is Swing Implemented This Way?

There are several advantages to executing all of the user interface code in a single thread:

  • Component developers do not have to have an in-depth understandingof thread programming. Toolkits like ViewPoint and Trestle, in which all components must fully support multithreaded access, can be difficult to extend, particularly for developers who are not expert at thread programming. Many of the toolkits developed more recently, such as SubArctic and IFC, have designs similar to Swing's.

  • Events are dispatched in a predictable order. The runnable objects queued byinvokeLater are dispatched from the same event queue as mouse and keyboard events, timer events, and paint requests. In toolkits where components support multithreaded access, componentchanges are interleaved with event processing at the whim of the thread scheduler. This makes comprehensive testing difficult or impossible.

  • Less overhead. Toolkits that attempt to lock critical sections carefullycan spend a substantial amount of time and space managing locks. Whenever the toolkit calls a method that might be implementedin client code (for example, anypublic or protected method in a public class), the toolkit must save its state and release all locks so that the client code can acquire locks if it needs to. When control returns from the method, the toolkit has to reacquire its locks and restore its state. All programs bear the cost of this, even though most programs do not require concurrent access to the GUI.

 

11.3 Using Timers in Swing Applications

Programs often need to schedule tasks to perform them repeatedly or perform them after a delay. An easy way to schedule tasks is to use a timer. As of J2SE v. 1.3, the Java platform provides twoTimer classes: one in the javax.swing packageand the other injava.util.

11.3.1 How Timers Work

Generally speaking, a timer enables a task to be executed either periodically or at a specific time. Timers are important, albeit specialized, tools for the GUI programmerbecause they simplify the job of scheduling activity that results in a screen update. GUI programs typically use timers for animation, such as for blinking a cursor, and for timing responses, such as displaying 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 thealarm function to schedule a SIGALRM signal; a signal handler can then perform the task. The Win32 API has functions, such asSetTimer, 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.

11.3.2 Code Without Timers

In programs written without timers, you'll see some rather nasty code for implementingdelays and periodic task execution. The nastiest algorithm of all is the busy wait loop, shown inListing 11-5. This little embarrassment attempts to create a delay by keeping the CPU busy, which is obviously a bad idea and likely to produce unpredictable results.
//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.

11.3.3 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. ThisTimer class is used by Swing components for things like blinking the text cursor and for timing the displayof tool-tips.

The Swing timer implementation fires an action event whenever the specified interval or delay time passes. You need to provide anAction 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 inListing 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 ownThread 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 explicitinvokeLater call.

11.3.4 The Utility Timer and TimerTask Classes

Timers aren't the exclusive domain of GUI programs. In J2SE v. 1.3, support for timers was added to thejava.util package. Like the Swing Timer class, the mainjava.util timer class is called Timer. (We'll call it the "utilityTimer class" to differentiate from the Swing Timer class.) Instead of schedulingAction objects,the utility Timer class schedules instances of a class calledTimerTask.

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 onTimerTask rather than on Timer. Still, both timer facilities have the same basic support for delayed and periodic execution. The most important difference betweenjavax.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 thejava.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 theTimer objects.

11.3.5 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 program, 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; eachjava.util.Timer object creates one thread. If your program requires large numbers of timers you might want to create severaljava.util.Timer objects and have each one schedule related TimerTasks. In a typical program you'll share just one globalTimer 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 aTimerTask 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 subclassSwingTimerTask and override its doRun method (instead ofrun).

abstract class SwingTimerTask extends java.util.TimerTask {    public abstract void doRun();    public void run() {        if (!EventQueue.isDispatchThread()) {            EventQueue.invokeLater(this);        } else {            doRun();        }    }}
Extending TimerTask

11.3.6 Timer Example

This example demonstrates an interesting use of timers. It displays an image and performs an animated cross-fade on the image when the user clicks a button. Selected frames of the animation are shown inFigure 11-1.
Cross-fade animation

This animation is implemented using the java.util.Timer andSwingTimerTask 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 thepaintComponent 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 thestartFade method, shown in Listing 11-10. This method creates an anonymous subclass of SwingTimerTask (Listing 11-9) that repeatedly callsrepaint. 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. Theschedule 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 theinitFrameRate 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 thepaintComponent 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 inThe Java Tutorial.2

11.4 Responsive Applications Use Threads

Although threads need to be used carefully, using threads is often essential to making a Swing program responsive. If the user-centric guidelines presented inSection 11.1 were distilled down to their developer-centric essence, the rule for handling user-initiated tasks would be:

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. TheSwingWorker finished method runs on the event-dispatching thread. Typically, you overridefinished to update the GUI based on the value produced by the construct method. (You can read more about the SwingWorker class onThe 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 forjava.sun.com, or an error string if something goes wrong. The finished method uses SwingWorker.get, which returns the value computed by theconstruct 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:

  • Keep the priority of worker threads low.
  • Keep the number of worker threads small.
  • Consider suspending worker threads during CPU-intensive operations like scrolling.

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

11.5 Example: Searching the Web

Some of the most popular web sites are the search engine portals like Yahoo! and AltaVista. The user interfaces for these web sites provide the results for a web search query in bundles of 10 to 20 query hits. To review more than the first bundle,the user clicks on a link that exposes another bundle. This can go on ad infinitum;queries often produce thousands of hits.

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.

11.5.1 Worker Thread Priority

Worker threads in the Search Party application are run at the lowest possible priority,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 implementationsmake 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.

11.5.2 Interrupting a Worker Thread

When the user presses the Search Party Stop button, all of the active worker threads are interrupted. The worker threads interpret the interrupt as a request to terminate: They close the network connection they're reading from, send any pending GUI updates to the event dispatching thread, and then exit.

When the Thread.interrupt method is called, it just sets the thread's interrupted boolean property. If the interrupted thread is sleeping or waiting, anInterruptedException is thrown. If the interrupted thread is blocked on I/O, anInterruptedIOException 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 callThread.isInterrupted before reading a character, this approach isn't reliable. TheisInterrupted 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 theInterruptedException (because it was waiting or sleeping) or if it clears theisInterrupted flag by calling Thread.interrupted, Search Party wouldn't realize that it's been interrupted! To make sure that Search Party detects interruptions, theSwingWorker interrupt method interrupts the worker thread andpermanently 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 anInterruptedIOException, 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. TheSearchWorker implementation catches and ignores the I/O exception that results from closing the thread's input stream while a read was pending.



Key Points

  • Using threads is essential for building responsive GUIs. Blocking user activity to wait for long tasks to complete leads to poor perceivedperformance.
  • The user is the boss. Always let your users know what's going on and give them regular status updates when waiting for long tasks to complete.
  • Once realized, Swing components should only be touched by code executing inside the AWT event-dispatch thread.
  • Use invokeLater and invokeAndWait to move work to the event dispatching thread.
  • Use timers for repeated operations. You can use either the javax.swing.Timer orjava.util.Timer. The utility Timer class gives you more control, but you have to move work to the event-dispatch thread yourself. You can use theSwingTimerTask utility described in this chapter to move work to the event-dispatch thread.
  • Use SwingWorker to execute time-consuming tasks on new threads and update the GUI on the event-dispatch thread.
  • Interrupt worker threads when the user is driving the system.
 

[Contents][Prev] [Next] [Index]

1

You can download the code for this and other examples fromhttp://java.sun.com/docs/books/performance/.

2

Mary Campione and Kathy Walrath, The Java Tutorial: Object-Oriented Programming for the Internet, Second Edition. Addison-Wesley, 1998.

3

Doug Lea, Concurrent Programming in Java: Design Principles and Patterns, Second Edition. Addison-Wesley, 1999.

4

Visit The Swing Connection online at http://java.sun.com/products/jfc/tsc/.

5

For more information about Sherlock, see http://www.apple.com/sherlock/.

6

For more information about Express Search, see http://express.go.com/.

Copyright © 2001, Sun Microsystems,Inc.. All rightsreserved.