|
Articles Index |
Threads Index
By Monica Pawlan
September 2001
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.
Example Application
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.
- Type into a text area
- Save text to a file
- Read text from a file
- Clear the text
- Cut, copy, and paste
- Cancel the file save or read
- 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:
Editor.readFromFile spawns a thread to read data
from a file and return user interface control to the user.
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.
Editor.setReadText sets text in
the user interface from the event dispatching thread.
Write to file operation:
Editor.writeToFile spawns a thread to write data
to a file and return user interface control to the user.
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.
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.
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);
}
|
Running the Example In Two Languages
The default properties file is a copy of either the English or Japanese
file. That way, the correct locale is loaded when the application
is invoked with either no command-line arguments, or command-line
arguments that do not map to an available properties file.
//compile
javac Editor.java compile
//run with no arguments
java Editor
Japanese Version
To run the Japanese version of the application using the
Japanese language properties file, type:
//compile
javac Editor.java
//run with arguments
java Editor ja JP
English Version
To run the English version using the English language properties
file, type:
//compile
javac Editor.java
//run with arguments
java Editor en US
See the
Internationalization
lesson in
Essentials of the
Java Programming Language: A Hands-On Guide for more information
Conclusion and Further Reading
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.
An excellent examples-based reference is
Java Thread Programming: The Authoritative
Solution by Paul Hyde.
This is an accessible and complete treatment that inexperienced and
experienced developers alike should find worthwhile.
The
Doing
Two or More Tasks at Once lesson in The Java Tutorial provides
excellent introductory coverage as well.
Also see
Concurrent Programming in Java
by Doug Lea and visit his
web site.
See these other related articles:
Acknowledgments
Many thanks to Naoto Sato who provided the Japanese unicode translations
for the Japanese version of the application, and to Paul Hyde and
Joseph Bowbeer who reviewed code and and answered questions during the
development phase for this article.
Monica Pawlan,
a staff writer for the Java Developer
Connection (JDC), is
author of Essentials of the Java Programming Language: A Hands-On
Guide (Addison-Wesley, 2000), and co-author of Advanced Programming
for the Java 2 Platform (Addison-Wesley, 2000).
Have a question about programming?
Use Java Online Support.
1 As used on this web site, the terms "Java
virtual machine" or "JVM" mean a virtual machine for the Java
platform.
|