Threads and Swing Core Java

As we mentioned in the introduction to this chapter, one of the reasons to use threads in your programs is to make your programs more responsive. When your program needs to do something time consuming, then you should fire up another worker thread instead of blocking the user interface.

However, you have to be careful what you do in a worker thread because, perhaps surprisingly, Swing is not thread safe. If you try to manipulate user interface elements from multiple threads, then your user interface can become corrupted.

When you click the Bad button, a new thread is started whose run method tortures a combo box, randomly adding and removing values.

Try it out. Click the Bad button. Click the combo box a few times. Move the scrollbar. Move the window. Click the Bad button again. Keep clicking the combo box. Eventually, you should see an exception report.

What is going on? When an element is inserted into the combo box, the combo box fires an event to update the display. Then, the display code springs into action, reading the current size of the combo box and preparing to display the values. But the worker thread keeps going—occasionally resulting in a reduction of the count of the values in the combo box. The display code then thinks that there are more values in the model than there actually are, asks for nonexistent values, and triggers an ArrayIndexOutOfBounds exception.

This situation could have been avoided by enabling programmers to lock the combo box object while displaying it. However, the designers of Swing decidednot to expend any effort to make Swing thread safe, for two reasons. First, synchronizationtakes time, and nobody wanted to slow down Swing any further.

More important, the Swing team checked out the experience other teams had with thread-safe user interface toolkits. What they found was not encouraging. Programmers using thread-safe toolkits turned out to be confused by the demands for synchronization and often created deadlock-prone programs.

Exception reports in the console

Exception reports in the console

Running Time-Consuming Tasks

When you use threads together with Swing, you have to follow two simple rules.

  • If an action takes a long time, do it in a separate worker thread and never in the event dispatch thread.
  • Do not touch Swing components in any thread other than the event dispatch thread.
    The reason for the first rule is easy to understand. If you take a long time in the event dispatch thread, the application seems “dead” because it cannot respond to any events. In particular, the event dispatch thread should never make input/output calls, which might block indefinitely, and it should never call sleep. (If you need to wait for a specific amount of time, use timer events.)

The second rule is often called the single-thread rule for Swing programming. These two rules seem to be in conflict with each other. Suppose you fire up a separatethread to run a time-consuming task. You usually want to update the user interface to indicate progress while your thread is working. When your task is finished, you want to update the GUI again. But you can’t touch Swing components from your thread. For example, if you want to update a progress bar or a label text, then you can’t simply set its value from your thread.

To solve this problem, you can use two utility methods in any thread to add arbitrary actions to the event queue. For example, suppose you want to periodically update a label in a thread to indicate progress. You can’t call label.setText from your thread.

Instead, use the invokeLater and invokeAndWait methods of the EventQueue class to have that call executed in the event dispatching thread.

Here is what you do. You place the Swing code into the run method of a class that implements the Runnable interface. Then, you create an object of that class and pass it to the static invokeLater or invokeAndWait method. For example, here is how to update a label text:

The invokeLater method returns immediately when the event is posted to the eventqueue. The run method is executed asynchronously. The invokeAndWait method waits untilthe run method has actually been executed.

In the situation of updating a progress label, the invokeLater method is more appropriate.
Users would rather have the worker thread make more progress than have the most precise progress indicator.

Both methods execute the run method in the event dispatch thread. No new thread is created.

java.awt.EventQueue

  • static void invokeLater(Runnable runnable)
    causes the run method of the runnable object to be executed in the event dispatch thread after pending events have been processed.
  • static void invokeAndWait(Runnable runnable)
    causes the run method of the runnable object to be executed in the event dispatch thread after pending events have been processed. This call blocks until the runmethod has terminated.
  • static boolean isDispatchThread()
    returns true if the thread executing this method is the event dispatch thread.

Using the Swing Worker

When a user issues a command for which processing takes a long time, you will want to fire up a new thread to do the work. As you saw in the preceding section, that thread should use the Event Queue .invoke Later method to update the user interface.

Several authors have produced convenience classes to ease this programming task, and one of these classes has made its way into Java SE 6. In this section, we describe that SwingWorker class.

You should try the program with a long file, such as the full text of The Count of Monte Cristo, supplied in the gutenberg directory of the book’s companion code.

The file is loaded in a separate thread. While the file is read, the Open menu item is disabled and the Cancel item is enabled (see Figure below). After each line is read, a line counter in the status bar is updated. After the reading process is complete, the Open menu item is reenabled, the Cancel item is disabled, and the status line text is set to Done.

This example shows the typical UI activities of a background task:

  • After each work unit, update the UI to show progress.
  • After the work is finished, make a final change to the UI.

The SwingWorker class makes it easy to implement such a task. You override the doInBackground method to do the time-consuming work and occasionally call publish to communicatework progress. This method is executed in a worker thread. The publish methodcauses a process method to execute in the event dispatch thread to deal with theprogress data. When the work is complete, the done method is called in the event dispatch thread so that you can finish updating the UI.

Loading a file in a separate thread

Loading a file in a separate thread

Whenever you want to do some work in the worker thread, construct a new worker. (Each worker object is meant to be used only once.) Then call the execute method. You will typically call execute on the event dispatch thread, but that is not a requirement.

It is assumed that a worker produces a result of some kind; therefore, SwingWorker<T, V>implements Future<T>. This result can be obtained by the get method of the Future interface. Since the get method blocks until the result is available, you don’t want to call it immediately after calling execute. It is a good idea to call it only when you know that the work has been completed. Typically, you call get from the done method. (There is no requirement to call get. Sometimes, processing the progress data is all you need.)

Both the intermediate progress data and the final result can have arbitrary types. The SwingWorker class has these types as type parameters. A SwingWorker<T, V>produces a result of type T and progress data of type V.

To cancel the work in progress, use the cancel method of the Future interface. When the work is canceled, the get method throws a CancellationException. As already mentioned, the worker thread’s call to publish will cause calls to process on the event dispatch thread. For efficiency, the results of several calls to publish may be batched up in a single call to process. The process method receives a List<V>containing all intermediate results.

Let us put this mechanism to work for reading in a text file. As it turns out, a JTextArea isquite slow. Appending lines from a long text file (such as all lines in The Count of MonteCristo) takes considerable time.

To show the user that progress is being made, we want to display the number of lines read in a status line. Thus, the progress data consist of the current line number and the current line of text. We package these into a trivial inner class: private class ProgressData

The final result is the text that has been read into a StringBuilder. Thus, we need a Swing- Worker<StringBuilder, ProgressData>. In the doInBackground method, we read a file, a line at a time. After each line, we call publish to publish the line number and the text of the current line.

We also sleep for a millisecond after every line so that you can test cancellation without getting stressed out, but you wouldn’t want to slow down your own programs by sleeping.

If you comment out this line, you will find that The Count of Monte Cristo loads quite quickly, with only a few batched user interface updates.

NOTE: You can make this program behave quite smoothly by updating the text area from the worker thread, but this is not possible for most Swing components. We show you the general approach in which all component updates occur in the event dispatch thread. In the process method, we ignore all line numbers but the last one, and we concatenate all

lines for a single update of the text area.

In the done method, the text area is updated with the complete text, and the Cancel menu item is disabled. Note how the worker is started in the event listener for the Open menu item. This simple technique allows you to execute time-consuming tasks while keeping the user interface responsive.

SwingWorkerTest.java

javax.swing.SwingWorker<T, V>

  • abstract T doInBackground()
    override this method to carry out the background task and to return the result of the work.
  • void process(List<V> data)
    override this method to process intermediate progress data in the event dispatch thread.
  • void publish(V... data)
    forwards intermediate progress data to the event dispatch thread. Call this method from doInBackground.
  • void execute()
    schedules this worker for execution on a worker thread.
  • SwingWorker.StateValue getState()
    gets the state of this worker, one of PENDING, STARTED, or DONE.

The Single-Thread Rule

Every Java application starts with a main method that runs in the main thread. In aSwing program, the main thread is short-lived. It schedules the construction of the user interface in the event dispatch thread and then exits. After the user interface construction, the event dispatch thread processes event notifications, such as calls to actionPerformed or paintComponent. Other threads, such as the thread that posts events

into the event queue, are running behind the scenes, but those threads are invisible to the application programmer. Earlier in the section, we introduced the single-thread rule: “Do not touch Swing components in any thread other than the event dispatch thread.” In this section, we investigate that rule further. There are a few exceptions to the single-thread rule.

  • You can safely add and remove event listeners in any thread. Of course, the listener methods will be invoked in the event dispatch thread.
  • A small number of Swing methods are thread safe. They are specially marked in the API documentation with the sentence “This method is thread safe, although most Swingmethods are not.” The most useful among these thread-safe methods are

NOTE: We used the repaint method many times in this book, but the revalidate method is lesscommon. Its purpose is to force a layout of a component after the contents have changed. Thetraditional AWT has a validate method to force the layout of a component. For Swing components, you should simply call revalidate instead. (However, to force the layout of a JFrame, you still need to call validate—a JFrame is a Component but not a JComponent.)

Historically, the single-thread rule was more permissive. Any thread was allowed toconstruct components, set their properties, and add them into containers, as long as none of the components had been realized. A component is realized if it can receive paint or validation events. This is the case as soon as the setVisible(true) or pack (!) methods have been invoked on the component, or if the component has been added to a container that has been realized.

That version of the single-thread rule was convenient. It allowed you to create the GUI of in the main method and then call setVisible(true) on the top-level frame of the application. There was no bothersome scheduling of a Runnable on the event dispatch thread.

Unfortunately, some component implementors did not pay attention to the subtleties of the original single-thread rule. They launched activities on the event dispatch thread without ever bothering to check whether the component was realized. For example, if you call setSelectionStart or setSelectionEnd on a JTextComponent, a caret movement is scheduled in the event dispatch thread, even if the component is not visible.

It might well have been possible to detect and fix these problems, but the Swing designers took the easy way out. They decreed that it is never safe to access components from any thread other than the event dispatch thread. Therefore, you need to construct the user interface in the event dispatch thread, using the call to EventQueue.invokeLater that you have seen in all our sample programs.

Of course, there are plenty of programs that are not so careful and live by the old version of the single-thread rule, initializing the user interface on the main thread. Those programs incur the slight risk that some of the user interface initialization causes actions on the event dispatch thread that conflict with actions on the main thread. As we said in earlier, you don’t want to be one of the unlucky few who run into trouble and waste time debugging an intermittent threading bug. Therefore, you should simply follow the strict single-thread rule.

You have now reached the end of Volume I of Core Java. This volume covered the fundamentals of the Java programming language and the parts of the standard library that you need for most programming projects. We hope that you enjoyed your tour through the Java fundamentals and that you found useful information along the way. For advanced topics, such as networking, advanced AWT/Swing, security, and internationalization, please turn to Volume II.


All rights reserved © 2018 Wisdom IT Services India Pvt. Ltd DMCA.com Protection Status

Core Java Topics