Fixing the Problem
Home ] Up ] Illustrating The Problem ] [ Fixing the Problem ]

 

 

Because you very often need to be able to update a Swing components from outside the event thread, Swing provides two useful methods which allow you to cause your code to run in the event loop.  They are static methods in the javax.swing.SwingUtilities class (also in java.awt.EventQueue):

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

In each case, you must provide an instance of a class which implements the Runnable interface.  Each method will cause the run() method of this class to be invoked asynchronously in the event dispatching thread.  It will be processed in turn, based on what other requests are pending in that thread.

An invokeLater() call will simply cause the scheduling of the Runnable class code.  

An invokeAndWait() call will will block the thread until all pending AWT events have been processed and (then) doRun.run() returns.  For obvious reasons, it should not be called from the event dispatch thread.

Example

Here's an example.  It is a modification of the first program to make the GUI update thread-safe.

package notPotentialCorruption;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

/**
 * This class invokes multiple threads which (incorrectly) update the contents
 * of a Swing component from outside the event thread.
 */

public class NotPotentialCorruptor extends JFrame
{
   //Construct the applet
   public NotPotentialCorruptor()
   {
      setTitle("Not Potential Corruptor Example");
      getContentPane().add( new ButtonPanel() );
      pack();
   }

   class ButtonPanel extends JPanel implements ActionListener
   {
      ButtonPanel()
      {
         setBackground(Color.blue);
         m_clickMeButton.addActionListener(this);
         add(m_clickMeButton);

         m_nowClickMeButton.addActionListener( new ActionListener()
         {
            public void actionPerformed(ActionEvent ev)
            {
               ButtonPanel.this.setBackground(Color.yellow);
            }
         }
         );
         m_nowClickMeButton.setEnabled(false);  // Disable for now
         add(m_nowClickMeButton);

         add( new JScrollPane(m_statusArea) );
      }

      public void actionPerformed(ActionEvent ev)
      {
         setBackground(Color.red);
         m_nowClickMeButton.setEnabled(true);
         for (int i = 1; i < 10; i++)
         {
            Thread thread = new CorruptorThread("Thread " + i);
            thread.start();
         }
      }

      private JButton m_clickMeButton = new JButton("Click me!");
      private JButton m_nowClickMeButton = new JButton("Now try to click me!");
      private JTextArea m_statusArea = new JTextArea(10, 20);

      class CorruptorThread extends Thread
      {
         CorruptorThread(String name)
         {
            super(name);
         }

         public void run()
         {
            for (int i = 0; i < 100; i++)
            {
               try
               {
                  Thread.sleep(1);
                  update("" + i);
               }
               catch (InterruptedException ie)
               {
                  ie.printStackTrace();
               }
            }

            update("Done");
         }

         private void update(final String text)
         {
            Runnable runnable = new Runnable()
            {
              public void run()
              {
                 String temp = m_statusArea.getText();
                 temp += "\n" + getName() + ": " + text;
                 m_statusArea.setText(temp);
              }
            };
            SwingUtilities.invokeLater(runnable);
         }
      }
   }

   public static void main(String[] args)
   {
      NotPotentialCorruptor frame = new NotPotentialCorruptor();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setVisible(true);
   }
}

Note that the code inside the update method has been enclosed within the run() method of an inner class which implements the Runnable interface.  The inner class is then passed into a call to the invokeLater() method.

Try It!

Here is an applet that invokes the above code when you click on its "Start" button:

Your browser does not have Java support. Please install the Java Plug-in. After you've brought the JFrame up,click on the "Click me!" button, and see what actually happens!

This program is not an example of what you should typically do.  It is merely a somewhat  extreme example of how to solve a GUI synchronization problem.

Results

I hope that you observe that there is no corruption in the JTextArea, and that the program eventually completes without incurring any corruption problems.

 

The page was last updated February 19, 2008