The thread priority in UI application

本文探讨了使用Swing创建高性能、响应迅速的图形用户界面的设计原则和技术。重点介绍了线程管理和事件处理的最佳实践,以及如何利用定时器来提高应用程序的交互性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[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 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/.

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

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, notably repaint 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 or Timer. 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 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:

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

  • Three 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.

  • 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 the 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.

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



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 by invokeLater 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, any public 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 two Timer classes: one in the javax.swing packageand the other in java.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 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.

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 in Listing 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. This Timer 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 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.

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 the java.util package. Like the Swing Timer class, the main java.util timer class is called Timer. (We'll call it the "utility Timer class" to differentiate from the Swing Timer class.) Instead of scheduling 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.

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; 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

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 in Figure 11-1.
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

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 in Section 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. 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:

  • 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, 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.



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 or java.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 the SwingTimerTask 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 from http://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.
<think>好的,我现在需要解决用户在鸿蒙开发中遇到的THREAD_BLOCK_6S问题。首先,我得回忆一下这个错误代码的可能含义。THREAD_BLOCK_6S听起来像是线程被阻塞了6秒,可能和HarmonyOS的线程管理机制有关。HarmonyOS有严格的线程监控,如果某个线程长时间阻塞主线程,系统会抛出这样的错误。 接下来,我需要分析可能的原因。常见的原因可能有几个方面:主线程执行耗时操作、死锁、资源竞争、或者第三方库的线程问题。主线程不应该进行网络请求、文件读写等耗时操作,否则会导致ANR(Application Not Responding)类似的问题。死锁的话,可能是多个线程互相等待对方释放锁,导致线程长时间挂起。资源竞争也可能导致线程无法及时释放资源,进而被阻塞。第三方库如果有自己的线程管理,可能和系统产生冲突,或者库本身有阻塞操作。 然后,解决步骤应该是逐步排查。首先检查代码中是否有在主线程执行耗时操作的地方,比如数据库操作、大量计算等。如果有,需要将这些操作移到子线程,比如使用TaskDispatcher来分发任务。接着,检查是否有同步锁或死锁的情况,可以用工具分析线程堆栈,看是否有线程在等待锁,或者多个线程互相等待。另外,查看是否有共享资源被长时间占用,导致其他线程无法访问,这时候可能需要优化资源使用策略,比如使用更细粒度的锁或者无锁数据结构。还要检查使用的第三方库,看它们的文档或社区是否有类似问题,或者尝试更新到最新版本。最后,使用DevEco Studio的调试工具,比如性能分析器,监控线程状态和CPU使用情况,定位具体阻塞点。 还需要考虑HarmonyOS的特定机制,比如它的线程模型和事件处理机制。例如,UI更新必须在主线程完成,但耗时操作必须放在其他线程。如果子线程没有正确结束或回调处理不当,也可能导致主线程阻塞。此外,查看官方文档是否有关于THREAD_BLOCK_6S的具体说明,或者是否有相关的日志信息可以提供更多线索。 总结下来,解决步骤应该是:1. 检查主线程耗时操作;2. 排查死锁和资源竞争;3. 检查第三方库;4. 使用工具分析线程状态;5. 参考官方文档和社区资源。同时,建议用户提供更多上下文信息,比如错误日志、相关代码片段,以便更精准地定位问题。</think>在鸿蒙开发中遇到`THREAD_BLOCK_6S`错误(线程阻塞超过6秒),通常是由于主线程(UI线程)被长时间阻塞导致。以下是逐步排查与解决方案: --- ### **1. 定位问题根源** - **错误表现**:应用卡顿或无响应,日志中抛出`THREAD_BLOCK_6S`。 - **常见原因**: - 主线程执行耗时操作(如文件读写、网络请求、复杂计算等)。 - 线程死锁或资源竞争。 - 第三方库未合理使用子线程。 --- ### **2. 核心解决步骤** #### **(1) 检查主线程耗时操作** - **关键原则**:所有耗时操作必须移至子线程。 - **代码示例**: ```java // 错误示例:在主线程执行耗时操作 public void onClick() { readLargeFile(); // 阻塞主线程 } // 正确示例:使用TaskDispatcher分发任务到子线程 public void onClick() { TaskDispatcher dispatcher = getUITaskDispatcher(); dispatcher.asyncDispatch(() -> { readLargeFile(); // 在子线程执行 }); } ``` #### **(2) 排查死锁或资源竞争** - **使用工具分析**: - 通过`DevEco Studio`的`Profiler`工具查看线程状态。 - 检查日志中线程堆栈信息(`StackTrace`),定位阻塞点。 - **代码检查**: - 避免嵌套同步锁(`synchronized`)。 - 使用`ConcurrentHashMap`等线程安全容器替代传统容器。 #### **(3) 检查第三方库** - **排查点**: - 确认第三方库是否在主线程执行初始化或回调。 - 查阅库文档,确保其线程模型符合鸿蒙规范。 - **解决方案**: - 通过`TaskDispatcher`封装库的调用逻辑。 - 联系库开发者或提交Issue。 #### **(4) 优化线程调度** - **鸿蒙线程模型**: - 使用`TaskDispatcher`管理线程(如`ParallelTaskDispatcher`、`SerialTaskDispatcher`)。 - 区分任务优先级(`HIGH`、`DEFAULT`、`LOW`)。 - **示例**: ```java // 创建并行任务分发器 TaskDispatcher dispatcher = AbilityShell.getContext().createParallelTaskDispatcher("myThread", TaskPriority.DEFAULT); dispatcher.asyncDispatch(() -> { // 子线程任务 }); ``` --- ### **3. 调试与验证** 1. **日志分析**: - 通过`HiLog`输出关键节点日志,确认阻塞位置。 ```java HiLog.info(LABEL, "Task started on thread: %{public}s", Thread.currentThread().getName()); ``` 2. **性能监控**: - 使用`DevEco Studio Profiler`监控CPU、内存和线程状态。 3. **复现与压测**: - 模拟高负载场景,验证线程阻塞是否彻底解决。 --- ### **4. 官方资源参考** - 查阅[鸿蒙线程管理文档](https://developer.harmonyos.com/cn/docs/documentation/doc-guides/thread-management-0000000000031822)。 - 关注[OpenHarmony社区](https://gitee.com/openharmony)的类似问题讨论。 --- ### **5. 扩展建议** - **异步编程规范**: - 使用`Promise`或`AsyncTask`简化异步逻辑。 - **避免过度线程化**: - 合理控制线程数量,防止资源浪费。 如果问题仍未解决,建议提供**错误日志片段**和**相关代码逻辑**,以便进一步定位!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值