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:
JComponent
: repaint
, revalidate
, invalidate
addXXXListener
and removeXXXListener
methodsIf 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 :
Worker
- inserts specific time intervals between changing colorsChangeColor
- updates the GUI by changing the background color of
a target componentRevertColor
- updates the GUI by changing the background color back
to its original colorpackage 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); } } }