Multithreaded Swing Applications - Online Article

Swing components are not inherently thread safe, and as a general rule, after Swing components have been made visible on the screen, you can only safely modify their data from the event thread. If you modify Swing component data from any thread other than the event dispatching thread, you must take precautions to ensure data integrity. An exception to this rule is the setText method on a JTextComponent or any of its subclasses, or any Swing component method whose documentation explicitly states it is thread safe.

Sometimes, you cannot avoid modifying Swing component data from outside the event dispatching thread after it has been made visible. An example is spawning a thread to handle a long operation such as reading or writing a large file over the net. The advantage to spawning a new thread is that the user interface is made available to the user for other operations during the long file operation. The spawned thread is a new thread, and therefore, not on the event dispatching thread even if it is spawned by an event dispatching thread method.

This article explains how to use the InvokeLater method and synchronize keyword to ensure data integrity when modifying data in Swing components. If you are new to multi-threaded programming, see Creating a Threaded Slide Show Applet for an introduction to the threaded applications.

The example is a simple text editor with very basic functionality. The point of this article is not to show you how to write a text editor, but to show you how to safely use threads to return control to the user interface while a long operation takes places. The example is internationalized and provides the basic operations listed below.

You can view the full source code in the Editor class file.

  1. Type into a text area
  2. Save text to a file.
  3. Read text from a file
  4. Clear the text
  5. Cut, copy, and paste
  6. Cancel the file save or read
  7. Localize the visible text

See Running the Example to find out how to compile and run the example in both English and Japanese.

Method Overview

In this example, the read and write operations are threaded to return control to the user interface so the user can elect to cancel either operation. The Editor class files also contain a Painter class to handle drawing messages in the left panel.

The Painter class methods that set and get the color or text for the left panel message use the synchronize keyword to prevent two threads from simultaneously modifying the same color or text and possibly corrupting the data.

The following subsections group and briefly explain the methods involved in threading the read and write operations and synchronizing data in the Painter class.

Note: See High-Level View of Application for a diagram of the read operation and an explanation of how the threaded read operation is implemented.

Threaded Operations

The following methods work together to return user interface control to the user during the read from file or write to file operation. They also ensure that text read from the file is set on the text area and text gotten from the text area and saved to the file are handled in a thread safe manner from the event dispatching thread.

Critical to ensuring that all Swing component modifications are made from the event dispatching thread are the SwingUtilities.invokeAndWait and SwingUtilities.invokeLater methods. These methods put a block of code on the event queue, and when the event dispatching thread gets to this code block, it executes the code. This means the GUI can be changed inside this block of code by the event dispatching thread.

Read from file Operation

  1. Editor.readFromFile spawns a thread to read data from a file and return user interface control to the user. 
  2. Editor.setReadTextSafely creates a Runnable to pass to InvokeLater so the text read from the file and the text read notification can be set on the user interface from the event dispatching thread. 
  3. Editor.setReadText sets text in the user interface from the event dispatching thread.

Write to file Operation

  1. Editor.writeToFile spawns a thread to write data to a file and return user interface control to the user.
  2. Editor.getWriteTextSafely spawns a thread to pass to InvokeLater so the text saved notification can be set on the user interface from the event dispatching thread.
  3. Editor.getWriteText sets text in the user interface from the event dispatching thread. 

Ensuring the Event Thread

Any modifications made to Swing components within the event-handling methods are made by the event dispatching thread and are perfectly thread safe. This program includes a method that checks whether an operation is on the event dispatching thread before allowing the operation to continue, as an extra safeguard to prevent a non-event dispatching thread from unsafely accessing a component or its data.

Editor.ensureEventThread

The following methods call Editor.ensureEventThread and will exit when called by a non-event-handling method:

Editor.cancelRead
Editor.cancelWrite
Editor.setReadText
Editor.getWriteText
Editor.writeToFile
Editor.readFromFile

Data Integrity

The synchronized keyword marks code blocks that work on class data. It tells the Java virtual machine (JVM1) to give the thread that called the synchronized code block a lock on the class data being worked on by the code block. This way no other thread can change the class data until the code block completes and the thread releases the lock.

The synchronized keyword can be applied to methods or blocks of code within a method. In this example, the get and set methods of the Painter class are all synchronized to prevent two threads calling them at the same time and colliding on the Painter class data.

Painter.setText
Painter.getText
Painter.setColor
Painter.getColor

High-Level View of Application

The Java VM creates and starts one event dispatching thread per application to call a Swing component's event handling methods. The actionPerformed and windowClosing methods are examples of event handling methods, and any modifications made to the Swing components from these and other event handling methods are completely thread safe.

The diagram below charts the read operation showing which method are called by the event dispatching thread and which are not. You can apply what you learn here to the write operations; they are nearly identical.

When the user invokes the read operation, the event dispatching thread calls the actionPerformed method in the

getTextMenuItem.addActionListener inner class.getTextMenuItem.addActionListener(
  new ActionListener() {
  public void actionPerformed(ActionEvent ev) {
  panel1paint.setText(messages.getString(
  "ReadNotification"));
  cancelwrite.setEnabled(false);
  cancelread.setEnabled(true);
  readFromFile();
  panel1paint.setColor(Color.blue);
  }
  });

While getTextMenuItem.addActionListener is not an event handling method, actionPerformed is.


The actionPerformed method calls the readFromFile method, which performs a double-check to be certain the read is being called by the event dispatching thread. When that check passes, a Runnable object is spawned to read the data from the file. A Runnable object allows a thread to be run within it.

Spawning the Runnable object returns user interface control to the user so he or she can invoke the Cancel operation.

The thread executing within the Runnable object sleeps for 5 seconds to simulate a long read over a slow network connection, and the text is read from the file.

private void readFromFile() {
  ensureEventThread();
  Runnable readRun = new Runnable() {
  public void run() {
  FileInputStream in=null;
  String text = null;
  try{
  //Simulate a slow read
  Thread.sleep(5000);
  String inputFileName = System.getProperty(
  "user.home")
  + File.separatorChar + "text.txt";
  File inputFile = new File(inputFileName);
  in = new FileInputStream(inputFile);
  byte bt[] =
  new byte[(int)inputFile.length()];
  in.read(bt);
  text = new String(bt);
  setReadTextSafely(text);
  } catch(java.lang.InterruptedException ex) {
  } catch(java.io.IOException e) {
  Object[] messageArguments = {
  messages.getString("file")
  };
  MessageFormat formatter= new MessageFormat(
  messages.getString("CannotReadError"));
  panel1paint.setColor(Color.blue);
  String cannotread = formatter.format(
  messageArguments);
  panel1paint.setText(messages.getString(
"CannotRead"));
  } finally {
  if(in != null) {
  try {
  in.close();
  } catch(java.io.IOException e) {
  panel1paint.setColor(Color.blue);
  panel1paint.setText(messages.getString(
  "CannotCloseError"));
  }
  }
  }
  }
  };
  readThread = new Thread(readRun);
  readThread.start();
  }

After the text is read, it needs to be set on the text area, but current execution is not on the event dispatching thread. To put the set operation onto the event dispatching thread, the setReadTextSafely method is called. The setReadTextSafely method spawns a Runnable object to pass to the InvokeLater method to put the setReadText method onto the event queue.

private void setReadTextSafely(String readtext) {
  final String text = readtext;
  Runnable r = new Runnable() {
  public void run() {
  try {
  setReadText(text);
  } catch (Exception ex) {
  ex.printStackTrace();
  }
  }
  };
  SwingUtilities.invokeLater(r);
  }

Internationalization

This example is internationalized and localized for English and Japanese. In an internationalized program, string values are read from a properties file that contains translations for the language in use in the form of key and value pairs. So, instead of creating strings directly in your code, you create a ResourceBundle that indicates the file where the translations are and reads the translations (values) from that file using the corresponding key.

For example, instead of creating the File menu like this,

  JMenu filemenu = new JMenu("File");

you would create the File menu as shown below where FileMenu is the key in the properties file with a corresponding value in the local language that means file:

  JMenu filemenu = new JMenu(
  messages.getString("FileMenu")).

An internationalized application can be adapted (localized) to as many cultures as needed. The locale information indicates the language, country, and variant of the language to use. For example, you might localize an application for the country Switzerland, the language German, and the Swiss German variant (the spoken dialect).

Properties Files

Properties files follow a naming convention so the application can locate and load the correct file at runtime. This example is internationalized and localized for English and Japanese, and includes the properties files listed below. See Running the Example In Two Languages below for information on how these files are used by the application at runtime.

MessagesBundle.properties is the default properties file and has no language and country information.

Note: A properties file that includes variant information for the German language spoken in Germany would look like this:

MessagesBundle_de_DE_EURO.properties 

Processing Locale Information

In this example, the main method is implemented to check for locale information in the form of language, country, and variant command-line information, or language and country only. If no command-line information is used, the default properties file is used. The code to create the user interface's frame passes the currentLocale information to the frame's constructor.

A call to ResourceBundle.getBundle("MessagesBundle", currentLocale) in the constructor determines which properties file to load for this application.

Editor(Locale currentLocale) {
  //Internationalization variables
messages = ResourceBundle.getBundle(
  "MessagesBundle",
  currentLocale);
  buildGUI();
  hookupEvents();
  }
  ...

  public static void main(String[] args){
  String language, country, variant;
  if(args.length == 3) {
  language = new String(args[0]);
  variant = new String(args[2]);
  currentLocale = new Locale(language,
  country,
  variant);
  } else if (args.length == 2) {
  language = new String(args[0]);
  country = new String(args[1]);
  currentLocale = new Locale(language, country);
  } else {
currentLocale = Locale.getDefault();
  }
  frame = new Editor(currentLocale);
  frame.setTitle(messages.getString( "Title"));
  WindowListener l = new WindowAdapter() {
  public void windowClosing(WindowEvent e) {
  System.exit(0);
  }
  };
  frame.addWindowListener(l);
  frame.pack();
  frame.setVisible(true);
  }

Conclusion

Working with Swing components requires care to ensure data integrity in your user interface. Swing components are not generally thread safe for performance reasons. However, if all your Swing component operations are done from the event dispatching thread, you have nothing to worry about.

In the event you have to access Swing components from a thread other than the event dispatching thread, you must use the InvokeLater or InvokeAndWait methods to put the operation on the event dispatching thread.

Always use the synchronized keyword to give a block of code a lock on data it is modifying to cover any possible case where it could be called from outside the event dispatching thread.

About the Author:

No further information.




Comments

No comment yet. Be the first to post a comment.