28.2 Multithreading Issues in Swing


As we mentioned at the beginning of the book, threading considerations play a very important role in Swing. This isn't too surprising if you think about the fact that Java is a highly multithreaded environment, but there is only a single display on which all components must render themselves in an organized, cooperative way. Some of the low-level code that interacts with peers in the native windowing system is not reentrant, which means that it can't be invoked safely while it's in the middle of doing something. Of course, that's exactly what might happen if different threads interact with it! Because of this fundamental limitation, there would have been no real benefit to undertaking the major effort of making the Java methods in the Swing components themselves thread-safe and indeed, with few exceptions, they are not.

In order to address this issue, Swing requires that all activity that interacts with components take place on a single thread, known as the event dispatch thread. This also allows Swing to consolidate repaints and otherwise improve the efficiency of the drawing process.

This restriction actually takes effect only after a component is realized, meaning that it's been painted on-screen or is ready to be painted. A top-level component is realized when it is made visible or has its pack( ) method called. At that point, any components it contains are also realized, and any components added to it later are immediately realized. This does mean, though, that it's safe to set up your application's interface components in your main application thread, as long as the last thing your code does to them is realize them. This is the way our code examples are structured. It is similarly safe to set up an applet's interface objects in its init( ) method.

However, this does place a burden on you as a developer to be aware of the threads that might invoke your methods. If it's possible that an arbitrary application thread can call a method that would interact with Swing components, that activity needs to be separated and deferred to a method that runs on the event dispatch thread.

By confining updates of component visual state to the event dispatch thread, you keep changes in sync with the repainting requests of the RepaintManager and avoid potential race conditions.

It is always safe to call the repaint( ) and revalidate( ) methods defined in JComponent. These methods arrange for work to be performed later in the event dispatch thread, using the mechanisms described below. It's also always safe to add or remove event listeners, as this has no effect on an ongoing event dispatch.

28.2.1 When Is Thread Safety an Issue?

It's important to understand when you need to worry about this and when you do not. In many situations, you'll update the state of your user interface only in response to various user events (such as a mouse click). In this case, you can freely update your components since your event-handling methods (in your listeners) are automatically invoked by the event dispatch thread.

The only time you have to worry about updating the state of a Swing component is when the request for the update comes from some other thread. The simplest (and very common) example is when you want to display an asynchronous progress bar. Or suppose you want a display driven by some outside process responsible for notifying your application when the world changes, such as a sports score ticker that shows the scores of a collection of games as they are being played. A separate server process might push new scores to your client program. This program would have some sort of socket (or higher-level protocol) listener thread to handle the input from the server. This new data needs to be reflected in the user interface. At this point, you'd need to ensure that the updates were made in the event dispatch thread.

Another common scenario involves responding to a user request that may take a long time to be processed. Such requests might need to access a database, invoke a remote method on an RMI or CORBA object, load new Java classes, etc. In situations like these, the event-handling thread must not be held up while lengthy processing takes place. If it is, the user interface would become completely unresponsive until the call completes. The correct approach is to execute the lengthy call in a separate thread and update the user interface when the call eventually returns or as information becomes available. Once again, this update is no longer happening in the event dispatch thread, so we need to do something special to make it work.

One strategy for implementing this type of call is to use a special "worker" thread responsible for executing the lengthy call and then updating the user interface. The Swing team provides a sample implementation of this idea in a class called SwingWorker. This class, as well as a discussion of its purpose, can be found on the Swing Connection at http://java.sun.com/products/jfc/tsc/articles/threads/threads2.html. We include a slightly simpler (but less reusable) example of the worker concept in InvokeExample.java below.

28.2.1.1 Don't be fooled

You might think that you'd be safe calling the various fireXX( ) methods defined by certain Swing components and models. However, it's important to remember that while these methods do create Event objects and send them to registered listeners, this does not imply that these events are executed in the event dispatch thread. In fact, the methods called on the listeners are invoked just like all other Java method calls; the argument just happens to be an event object. So even if you're updating the user interface from another thread by firing some sort of event (perhaps to indicate that the model's state has changed), you still need to use the methods described below to ensure that the processing takes place in the event dispatch thread.

28.2.2 Updating Components from the Event Dispatch Thread

Swing provides two methods that allow you to execute code in the event-dispatch thread. These are static methods, defined by SwingUtilities, called invokeAndWait( ) and invokeLater( ). Both of these methods allow you to execute an arbitrary block of code in the event dispatch thread. The invokeAndWait( ) method blocks until the code has completed executing while invokeLater( ) just adds your code to the event queue and returns immediately. You should usually use invokeLater( ) rather than invokeAndWait( ).

You may be wondering how Swing manages to add arbitrary code to the event queue. Here's how it works. Both of the methods we just mentioned take a single Runnable as a parameter. This Runnable may be defined to do whatever you want. Typically, the Runnable's run( ) method performs some sort of update to the state of one or more components. When SwingUtilities receives the Runnable, it passes it to a class called SystemEventQueueUtilities, which wraps it in an instance of a special AWTEvent subclass (a private inner class called SystemEventQueueUtilities.RunnableEvent) and adds the new event to the system event queue. When the event thread gets around to running the special event, the processing of the event results in the Runnable's run( ) method being executed.

Figure 28-4 shows a more detailed breakdown of what's going on under the hood. Once the event is posted to the system event queue, the call to invokeLater( ) returns immediately. If this were an invokeAndWait( ) call, the calling thread would wait( ) until the event was executed, and then the processRunnableEvent( ) method would notify( ) the waiting thread that the Runnable had been executed. Only then would invokeAndWait( ) return. Note that SystemEventQueueUtilities and its inner classes RunnableEvent and RunnableTarget are non-public. We show them here only to give you an understanding of what's going on behind the scenes. The EventQueue class is part of java.awt.

Figure 28-4. SwingUtilities.invokeLater( ) (under the hood)
figs/swng2.2804.gif
28.2.2.1 Methods

Here are the method signatures for the SwingUtilities methods we just described. The rest of the SwingUtilities class was described in Chapter 27.

public static void invokeAndWait(Runnable doRun) throws InterruptedException, InvocationTargetException

Place a special event onto the system event queue. When processed, this event simply executes doRun's run( ) method. The invokeAndWait( ) call does not return until the run( ) method finishes. If the run( ) method throws an exception, it is caught, and an InvocationTargetException is thrown by invokeAndWait( ). An InterruptedException is thrown if the execution of the Runnable's run( ) method is interrupted for any reason.

public static void invokeLater(Runnable doRun)

Place a special event onto the system event queue. When processed, this event simply executes doRun's run( ) method. The invokeLater( ) call returns immediately after the event has been added to the queue. Note that any exceptions thrown by the Runnable's run( ) method are caught and ignored.

There are a number of caveats surrounding the use of invokeAndWait( ) that don't apply when using invokeLater( ), which is why Sun recommends you use the latter. Fundamentally, invokeAndWait( ) causes your thread to block for a potentially long time, so you need to be careful that it does not hold any locks that might be needed by other threads, or performance will suffer (in the worst case, deadlock might even cause your application to freeze). Also, if you try to call this method, and you happen to be running in the event dispatch thread, you'll be rewarded with an exception.

If you can't tell by inspecting your code whether a particular method will always (or never) be called by the event dispatch thread, you can check using SwingUtilities.isEventDispatchThread( ) and take the appropriate action at runtime.

For situations in which you want to update your user interface at scheduled or periodic intervals, you'll likely find the javax.swing.Timer class helpful. It manages all these threading issues for you, providing a nice programmatic interface.

28.2.2.2 Managing synchronization properly

Here is an example that shows three ways you might try to invoke a long-running method in response to a user-generated event. Only one of these strategies works correctly. The other two show the types of things you should avoid.

// InvokeExample.java // import javax.swing.*; import java.awt.*; import java.awt.event.*; public class InvokeExample {   private static JButton good = new JButton("Good");   private static JButton bad = new JButton("Bad");   private static JButton bad2 = new JButton("Bad2");   private static JLabel resultLabel = new JLabel("Ready", JLabel.CENTER);   public static void main(String[] args) {     JFrame f = new JFrame( );     f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);     // Layout     JPanel p = new JPanel( );     p.setOpaque(true);     p.setLayout(new FlowLayout( ));     p.add(good);     p.add(bad);     p.add(bad2);          Container c = f.getContentPane( );     c.setLayout(new BorderLayout( ));     c.add(p, BorderLayout.CENTER);     c.add(resultLabel, BorderLayout.SOUTH);     // Listeners     good.addActionListener(new ActionListener( ) {       public void actionPerformed(ActionEvent ev) {         resultLabel.setText("Working . . . ");         setEnabled(false);         // We're going to do something that takes a long time, so we spin off a         // thread and update the display when we're done.         Thread worker = new Thread( ) {           public void run( ) {             // Something that takes a long time. In real life, this might be a DB             // query, remote method invocation, etc.             try {               Thread.sleep(5000);             }             catch (InterruptedException ex) {}             // Report the result using invokeLater( ).             SwingUtilities.invokeLater(new Runnable( ) {               public void run( ) {                 resultLabel.setText("Ready");                 setEnabled(true);               }             });           }         };         worker.start( ); // So we don't hold up the dispatch thread       }     });     bad.addActionListener(new ActionListener( ) {       public void actionPerformed(ActionEvent ev) {         resultLabel.setText("Working . . .");         setEnabled(false);         // We're going to do the same thing, but not in a separate thread.         try {           Thread.sleep(5000); // The dispatch thread is starving!         }         catch (InterruptedException ex) {}         // Report the result.         resultLabel.setText("Ready");         setEnabled(true);       }     });     bad2.addActionListener(new ActionListener( ) {       public void actionPerformed(ActionEvent ev) {         resultLabel.setText("Working . . . ");         setEnabled(false);         // The wrong way to use invokeLater( ). The runnable( ) shouldn't starve the         // dispatch thread.         SwingUtilities.invokeLater(new Runnable( ) {           public void run( ) {             try {               Thread.sleep(5000); // The dispatch thread is starving!             }             catch (InterruptedException ex) {}             resultLabel.setText("Ready");             setEnabled(true);           }         });       }     });     f.setSize(300, 100);     f.setVisible(true);   }   // Allows us to turn the buttons on or off while we work   static void setEnabled(boolean b) {     good.setEnabled(b);     bad.setEnabled(b);     bad2.setEnabled(b);   } } 

In the first listener ("Good"), we use a worker thread to execute our lengthy process. In this thread's run( ) method, we execute our code (in this case, just a sleep( ) call) and then use invokeLater( ) to update the display. This is the proper strategy.

In the next listener ("Bad"), we show what happens if we run this code directly in the event listener. As you'd expect, any attempt to resize or repaint the display fails while we have taken over the dispatch thread.

Finally, we show another incorrect attempt ("Bad2"). In this case, we use invokeLater( ), but we use it incorrectly. The problem here is that the run( ) method called by invokeLater( ) takes a long time to execute. It's just as bad to have a long-running method in an invokeLater( ) call as it is to perform it directly in your event listener code: they are both run in the event dispatch thread the whole point of invokeLater( ) being to run your code on the event dispatch thread!

Figure 28-5 shows the display after clicking each of the three buttons and then covering part of the display with another window. In the first bad case, our label update and the disabling of the buttons doesn't even occur, since we never got back into the event queue to let those updates happen. In the second bad case, those things are able to occur before we put the dispatch thread to sleep, but we have still made a big mess of things by holding up the event queue.

Figure 28-5. Correct and incorrect management of the event dispatch thread
figs/swng2.2805.gif

When you run this example, try resizing the frame (or covering it with another window) to see how the different strategies perform. The key lesson here is that the system event queue uses a single thread to execute events, so any sort of long-running processing should be kept out of the event queue.



Java Swing
Graphic Java 2: Mastering the Jfc, By Geary, 3Rd Edition, Volume 2: Swing
ISBN: 0130796670
EAN: 2147483647
Year: 2001
Pages: 289
Authors: David Geary

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net