Swing threads

Event Dispatch Thread

In a Swing application, most of the processing takes place in a single, special thread called the event dispatch thread (EDT).

This thread becomes active after a component becomes realized: either pack, show, or setVisible(true) has been called. When a top level window is realized, all of its components are also realized. Swing is mostly single-threaded: almost all calls to realized components should execute in the event dispatch thread. The thread-safe exceptions are:

Worker Threads Keep GUIs Responsive

If a task needs a relatively long time to complete, then performing that task in the event dispatch thread will cause the user interface to become unresponsive for the duration of the task - the GUI becomes "locked". Since this is undesirable, such tasks are usually performed outside the event dispatch thread, on what is commonly referred to as a worker thread.

When a worker thread completes its task, it needs a special mechanism for updating realized GUI components, since, as stated above, realized components almost always need to be updated only from the event dispatch thread.

With modern JDK's, the most common way of doing this is the SwingWorker class, introduced in JSE 6. It should be used if available.

For using timers, see this related topic.

Example 1

The FetchQuotesAction fetches stock price information. This is a good example of an operation which should be on a separate thread. Since it's not known how long it will take, or even if a good web connection is present, it's an operation which could very easily lock the GUI.

Below are the pertinent parts of its code. Note how a SwingWorker is divided into two parts: doInBackground does the main work and returns any needed data, and done first calls get to access the data, and then updates the user interface.


/**
* Fetch current quote data for the {@link CurrentPortfolio} from a data 
* source on the web.
*/
public final class FetchQuotesAction extends AbstractAction implements Observer {

  //elided...

  /** 
   Fetch quotes from the web for the <tt>CurrentPortfolio</tt>.
   This is called either explicitly, or periodically, by a Timer.
  */
  @Override public void actionPerformed(ActionEvent e) {
    fLogger.info("Fetching quotes from web.");
    fSummaryView.showStatusMessage("Fetching quotes...");
    SwingWorker<List<Quote>, Void> hardWorker = new HardWorker();
    hardWorker.execute();
  }
  
  /**
  * The set of {@link Stock} objects in which the user 
  * is currently interested.
  */
  private CurrentPortfolio fCurrentPortfolio;

  /**
  * GUI element which is updated whenever a new set of quotes is obtained.
  */
  private SummaryView fSummaryView;

  //elided...
  
  private final class HardWorker extends SwingWorker<java.util.List<Quote>, Void> {
    @Override  protected List<Quote> doInBackground() throws Exception {
      List<Quote> result = null;
      try {
        result = fCurrentPortfolio.getPortfolio().getQuotes();
      }
      catch(DataAccessException ex){
        ex.printStackTrace();
      }
      return result;
    }
    @Override protected void done() {
      try {
        //get the data fetched above, in doInBackground()
        List<Quote> quotes = get();
        if (quotes != null){
          showUpdated(quotes);
        }
        else {
          fSummaryView.showStatusMessage("Failed - Please connect to the web.");
        }
      }
      catch (Exception ex) {
        ex.printStackTrace();
      }
    }
  }
}
 

JDK < 6

Other techniques are needed when using older versions of the JDK. Two methods of the EventQueue class, invokeLater and invokeAndWait, are provided for this purpose (SwingUtilities has synonymous methods as well). Sun recommends using invokeLater as the usual preferred style.

When using threads in this way, it is usually a good idea to use daemon threads, not user threads, whenever possible: daemon threads will not prevent an application from terminating. Since threads are user threads by default, an explicit call to Thread.setDaemon(true) is required.

The remaining examples use JSE 1.5.

Example 2

The Splash Screen topic (and in particular its Launcher class) is a good example of using a worker thread. Here, the status of the launch thread as a worker thread is exploited to show a splash screen to the user, but only until the main window has finished loading.

Example 3

The ColorTip class, shown below, changes the background color of a component for a short, fixed interval of time, as a simple way of calling attention to that component.

Its worker thread does not work very hard - it sleeps a lot. The calls to sleep do not cause the GUI to become unresponsive, however, since these calls do not take place in the event dispatch thread.

ColorTip has three private, Runnable nested classes :



package hirondelle.stocks.quotes;

import hirondelle.stocks.util.Args;
import hirondelle.stocks.util.Consts;
import hirondelle.stocks.util.Util;

import java.awt.Color;
import java.awt.EventQueue;
import java.util.logging.Logger;

import javax.swing.JComponent;

/**
* Calls user's attention to an aspect of the GUI (much like a 
* <tt>ToolTip</tt>) by changing the background color of a 
* component (typically a <tt>JLabel</tt>) for a few seconds; 
* the component will always revert to its original background color 
* after a short time has passed. This is done once, without repeating.
*
* <p>Example use case:
<pre>
 //no initial delay, and show the new color for 2 seconds only
 ColorTip tip = new ColorTip(0, 2, someLabel, temporaryColor);
 tip.start();
</pre>
* 
* Uses a daemon thread, so this class will not prevent a program from 
* terminating. Will not lock the GUI.
*/
final class ColorTip {
  
  /**
  * Constructor. 
  *  
  * @param aInitialDelay number of seconds to wait before changing the 
  * background color of <tt>aComponent</tt>, and must be in range 0..60 (inclusive).
  * @param aActivationInterval number of seconds to display <tt>aTempColor</tt>, 
  * and must be in range 1..60 (inclusive).
  * @param aComponent GUI item whose background color will be changed.
  * @param aTempColor background color which <tt>aComponent</tt> will take for 
  * <tt>aActivationInterval</tt> seconds.
  */
  ColorTip (
    int aInitialDelay, int aActivationInterval, JComponent aComponent, Color aTempColor
   ) {
    Args.checkForRange(aInitialDelay, 0, Consts.SECONDS_PER_MINUTE);
    Args.checkForRange(aActivationInterval, 1, Consts.SECONDS_PER_MINUTE);
    Args.checkForNull(aTempColor);
    fInitialDelay = aInitialDelay;
    fActivationInterval = aActivationInterval;
    fComponent = aComponent;
    fTemporaryColor = aTempColor;
    fOriginalColor = aComponent.getBackground();
    fOriginalOpacity = aComponent.isOpaque();
  }

  /**
  * Temporarily change the background color of the component, without interfering with 
  * the user's control of the gui, and without preventing program termination.
  *
  * <P>If the target temporary color is the same as the current background color, then 
  * do nothing. (This condition occurs when two <tt>ColorTip</tt> objects are 
  * altering the same item at nearly the same time, such that they "overlap".)
  */
  void start(){
    if (isSameColor()) return;
    /*
     * The use of a low-level Thread, instead of a more modern class, is unusual here.
     * It's acceptable since other tools aren't a great match for this task, which is to 
     * go back and forth TWICE (wait, color-on, wait, color-off) between a worker thread
     * and the Event Dispatch Thread; that's not a good match for either SwingWorker 
     * or javax.swing.Timer.
     */
    Thread thread = new Thread(new Worker());
    thread.setDaemon(true);
    thread.start();
  }
  
  // PRIVATE 
  private final int fInitialDelay;
  private final int fActivationInterval;
  private final JComponent fComponent;
  private final Color fTemporaryColor;
  private final Color fOriginalColor;
  private final int fCONVERSION_FACTOR = Consts.MILLISECONDS_PER_SECOND;
  
  /**
  * Stores the original value of the opaque property of fComponent.
  *  
  * Changes to the background color of a component 
  * take effect only if the component is in charge of drawing its background.
  * This is defined by the opaque property, which needs to be true for these
  * changes to take effect.
  *
  * <P>If fComponent is not opaque, then this property is temporarily
  * changed by this class in order to change the background color.
  */
  private final boolean fOriginalOpacity;
  
  private static final Logger fLogger = Util.getLogger(ColorTip.class);

  /**
  * Return true only if fTemporaryColor is the same as the fOriginalColor. 
  */
  private boolean isSameColor(){
    return fTemporaryColor.equals(fOriginalColor);
  }
  
  /** 
   The sleeping done by this class is NOT done on the Event Dispatch Thread; 
   that would lock the GUI. 
  */
  private final class Worker implements Runnable {
    @Override public void run(){
      try {
        fLogger.fine("Initial Sleeping...");
        Thread.sleep(fCONVERSION_FACTOR * fInitialDelay);
        EventQueue.invokeLater(new ChangeColor());
        fLogger.fine("Activation Sleeping...");
        Thread.sleep(fCONVERSION_FACTOR * fActivationInterval);
        EventQueue.invokeLater(new RevertColor());
      }
      catch (InterruptedException ex) {
        fLogger.severe("Cannot sleep.");
      }
      fLogger.fine("Color worker done.");
    }
  }
  
  private final class ChangeColor implements Runnable {
    @Override public void run(){
      if (! fOriginalOpacity) {
        fComponent.setOpaque(true);
      }
      fComponent.setBackground(fTemporaryColor);
    }
  }
  
  private final class RevertColor implements Runnable {
    @Override public void run(){
      fComponent.setBackground(fOriginalColor);
      fComponent.setOpaque(fOriginalOpacity);
    }
  }
}
 



See Also :
Splash screen
Timers
Would you use this technique?
Yes   No   Undecided   
© 2014 Hirondelle Systems | Source Code | Contact | License | RSS
Individual code snippets can be used under this BSD license - Last updated on September 21, 2013.
Over 2,000,000 unique IPs last year - Built with WEB4J.
- In Memoriam : Bill Dirani -