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

 

 

Here's a program that breaks the Swing single thread rule:

package potentialCorruption;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;

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

public class PotentialCorruptor extends JFrame
{
  /**
   * Main entry point for Java application
   */
  public static void main(String[] args)
  {
    PotentialCorruptor frame = new PotentialCorruptor();
    frame.setVisible(true);
  }
  
  /**
   * Construct the class
   */
  public PotentialCorruptor()
  {
    setTitle("Potential Corruptor Example");
    setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
    
    Container panel = getContentPane();
    panel.setLayout( new BorderLayout() );
    m_buttonPanel = new ButtonPanel();
    panel.add(m_buttonPanel, BorderLayout.CENTER);
    
    m_statusLabel.setFont( new Font( "dialog", Font.BOLD, 14 ) );
    panel.add(m_statusLabel, BorderLayout.SOUTH);
    pack();
  }
  
  /**
   * Button Panel
   */
  class ButtonPanel extends JPanel
  {
    ButtonPanel()
    {
      setBackground(Color.blue);
      m_startButton.addActionListener( new ActionListener()
        {
          public void actionPerformed(ActionEvent ev)
          {
            setBackground(Color.red);
            m_turnYellowButton.setEnabled(true);
            m_textArea.setText("");
            m_statusLabel.setText("Running...");
            for (int i = 1; i < TOTAL_THREADS; i++)
            {
              Thread thread = new CorruptorThread("Thread " + i);
              thread.start();
            }
          }
        }
      );
      add(m_startButton);
      
      m_turnYellowButton.addActionListener( new ActionListener()
        {
          public void actionPerformed(ActionEvent ev)
          {
            ButtonPanel.this.setBackground(Color.yellow);
          }
        }
      );
      m_turnYellowButton.setEnabled(false);  // Disable for now
      add(m_turnYellowButton);
      
      add( new JScrollPane(m_textArea) );
    }
    
    /**
     * The thread to run
     */
    class CorruptorThread extends Thread
    {
      CorruptorThread(String name)
      {
        super(name);
      }
      
      public void run()
      {
        int threadNumber = m_threadCount.incrementAndGet();
        m_statusLabel.setText("Running (" + threadNumber + " threads)");
        for (int i = 0; i < 50; i++)
        {
          try
          {
            Thread.sleep(1);
            update("" + i);
          }
          catch (InterruptedException ie)
          {
            ie.printStackTrace();
          }
        }
        
        update("Thread Done");
        threadNumber = m_threadCount.decrementAndGet();
        m_statusLabel.setText("Running (" + threadNumber + " threads)");
        if (threadNumber == 0)
        {
          m_statusLabel.setText("Done!");
          m_buttonPanel.setBackground(Color.blue);
          m_turnYellowButton.setEnabled(false);
        }
      }
      
      private void update(String text)
      {
        String temp = m_textArea.getText();
        temp += "\n" + getName() + ": " + text;
        m_textArea.setText(temp);
      }
    }
  }
    
  private ButtonPanel m_buttonPanel;
  private JButton m_startButton = new JButton("Start (Turn Red)");
  private JButton m_turnYellowButton = new JButton("Now Turn Yellow");
  private JTextArea m_textArea = new JTextArea(10, 20);
  private JLabel m_statusLabel = new JLabel("", SwingConstants.CENTER);
  private static final int TOTAL_THREADS = 10;
  private AtomicInteger m_threadCount = new AtomicInteger(0);
}

Note that the program fires up 9 threads, each of which repeatedly sleeps, wakes up, updates the GUI (specifically a text area), and goes back to sleep again, exiting after a relatively large number of iterations.

Note: I have specifically avoided using the JTextArea's append() method, because, unlike most Swing methods, it is thread-safe.  Instead, I used a combination of getText() and setText() methods to exhibit the behavior.

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 up the JFrame, click on the "Now Turn Yellow" button, and see what actually happens!

Please be patient!  This applet takes some time to complete.

Results

Did you notice that the contents of the JTextArea occasionally get corrupted?  Here's a small example I observed when I ran the program.  It's a snippet I copied and pasted from the JTextArea:

Thread 2: 13
Thread 7: 9
Thread 4: 12: 0
Thread 6: 0
Thread 2: 2

Even worse, you might experience more serious problems.  I encountered at least one occasion when the entire GUI locked up, and became totally unresponsive.  Probably, there was some internal corruption taking place.

 

The page was last updated February 19, 2008