Вы находитесь на странице: 1из 10

Core Java Technologies Tech Tips

Tips, Techniques, and Sample Code

Welcome to the Core Java Technologies Tech Tips for July 27,
2005. Here you'll get tips on using core Java technologies and
APIs, such as those in Java 2 Platform, Standard Edition (J2SE).
This issue covers:
* Swing "Urban Legends"
* From Runtime.exec() to ProcessBuilder
These tips were developed using Java 2 Platform Standard
Edition Development Kit 5.0 (JDK 5.0). You can download JDK 5.0
at http://java.sun.com/j2se/1.5.0/download.jsp.
This issue of the Core Java Technologies Tech Tips is written by
John Zukowski, president of JZ Ventures, Inc.
(http://www.jzventures.com).
You can view this issue of the Tech Tips on the Web at
http://java.sun.com/developer/JDCTechTips/2005/tt0727.html
See the Subscribe/Unsubscribe note at the end of this newsletter
to subscribe to Tech Tips that focus on technologies and products
in other Java platforms.
For more Java technology content, visit these sites:
java.sun.com - The latest Java platform releases, tutorials, and
newsletters.
java.net - A web forum for collaborating and building solutions
together.
java.com - Hot games, cool apps -- Experience the power of Java
technology.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SWING "URBAN LEGENDS"
Developers who work with Swing components often hear about
certain ways of doing things that they assume are the right ways
to work with Swing. Like "urban legends" that purport to be
accounts of actual events but never really happened, some of
these Swing techniques are incorrect. In this tip, you'll learn
about a number of these Swing urban legends -- approaches that
will hinder the performance of your Swing applications. In some
cases, the performance reduction might only be nanoseconds, but
if you want the best performance profile, even cutting a handful
of nanoseconds adds up over time.

Here are three Swing urban legends:


o Create threads for long tasks from the event dispatch thread.
o Use SwingUtilities for running tasks on the event dispatch
thread.
o Synchronize methods for synchronization.
Create Threads for Long Tasks From the Event Dispatch Thread
Here's the situation: you want to spin off a thread from the
event queue to do a long task. If your event handler needs to do
a long task, you don't want to block the event thread. So you
create a new thread for the long task, and call
invokeLater() when the task is done to handle the results on the
event thread. Here's the typical usage pattern:
public void actionPerformed(ActionEvent e) {
Runnable longTask = new Runnable() {
public void run() {
// Run task to do long stuff
... // long task
// Update Swing component when done
Runnable awtTask = new Runnable() {
public void run() {
// Update Swing component
}
}
EventQueue.invokeLater(awtTask);
}
};
Thread t = new Thread(longTask);
t.start();
}
This is a common pattern: run long tasks off the event dispatch
thread, then update the Swing component after the long task is
done. It seems like the right thing to do, but it isn't.
If you follow this pattern, you'll run across a problem that
could cause your program to behave badly. When a new thread is
created, it retains the thread priority of the creating thread.
Because the event thread typically runs at a higher level than
normal threads, threads created from the event thread inherit
the higher priority.
Here is a simple program, Threads, that demonstrates the thread
priorities:
import java.awt.*;
public class Threads {
public static void main(String args[]) {
System.out.println("Main Thread priority: " +
Thread.currentThread().getPriority());
Runnable runner = new Runnable() {
public void run() {
System.out.println("Event Thread priority: " +
Thread.currentThread().getPriority());
}
};

EventQueue.invokeLater(runner);
}
}
If you run Threads, you'll see that the main thread has
a priority of 5, and the event thread runs at a priority 6.
>> java Threads
Main Thread priority: 5
Event Thread priority: 6
The higher priority for the event thread is desirable. You want
your user interfaces to be responsive. But, you don't want to
extend that higher priority to non-event processing tasks. So be
sure to lower the priority of user-created threads initialized
from the event dispatch thread. This means:
Change:
Thread t = new Thread(longTask);
t.start();
To:
Thread t = new Thread(longTask);
t.setPriority(Thread.NORM_PRIORITY);
t.start();
Threads created with this new priority will not compete for
processing time with the event dispatch thread. If there is
something to run on the event dispatch thread, it will win -not the worker thread. You might consider creating
a WorkerThread class for just this purpose. That way you
won't have to keep calling setPriority() for all new threads
created from the event dispatch thread, or use a thread pool
through the following new classes in the
java.util.concurrent package:
o Executors.newCachedThreadPool()
For a thread pool with unbound size
o Executors.newFixedThreadPool(int size)
For a thread pool of fixed size > 1
o Executors.newSingleThreadExecutor()
For a thread pool of fixed size = 1
Executor was added to the standard libraries with JDK 5.0.
Use SwingUtilities For Running Tasks on the Event Dispatch Thread
This isn't really an urban legend, but rather an explanation of
the use of the EventQueue class in the first legend. Many people
are familiar with the SwingUtilities class for the use of
invokeLater() and invokeAndWait(). Where did this EventQueue
class come from?
The answer is that all these methods in SwingUtilities wrap

calls to the same methods of the EventQueue class in the


java.awt package. In other words, there is a level of
indirection of the method calls when used through
SwingUtilities. There is technically nothing wrong with using
the methods. It's just that you can avoid the indirection by
using the EventQueue methods directly.
Note that another method that wraps access to the EventQueue
class is isEventDispatchThread(). This is used to check if the
current task is running on the event dispatch thread.
If the SwingUtilities methods are just wrapper methods, why do
they exist? When the Swing components became available with
JDK 1.2, Sun released a version that worked with JDK 1.1. All
the Swing bits needed to be self-contained. In other words, the
Swing classes couldn't use anything that was introduced to
JDK 1.2. For that reason, SwingUtilities contains the methods
for invokeLater and invokeAndWait. Since those methods simply
pass along the method calls to EventQueue, you should call the
EventQueue methods directly.
Synchronize Methods for Synchronization
Another commonly-seen practice that hinders performance involves
the use of the synchronized keyword. It is common to synchronize
methods to prevent simultaneous execution. In many cases, this
approach is fine. When might having synchronized methods be bad
and slow down performance? When the class is a subclass of an
AWT or Swing component, specifically any subclass of
java.awt.Component.
What's wrong with synchronizing methods in subclasses of
Component? If you are only trying to synchronize access to your
methods, having a synchronized method means you are competing
with all the other synchronized methods of Component. This
causes your method to be blocked when it shouldn't, and other
Component methods to be blocked when they shouldn't. In fact,
this could also lead to unexpected deadlocks.
Instead of synchronizing at the method level, you can create
a lock variable that is shared by the methods that need to be
synchronized. Here's an example:
public class Foo extends JComponent {
private final Object lock = new Object();
private final char[] chars;
public void setMethod(String value) {
synchronized(lock) {
// save off value as chars
chars = value.toCharArray();
}
}
public String getMethod() {
synchronized(lock) {
// regenerate saved value from chars
String savedValue = new String(chars);

return savedValue;
}
}
}
For some more insights into Swing performance, see the
transcript of the SDN chat, Getting High Performance from Your
Desktop Client
(http://java.sun.com/developer/community/chat/JavaLive/2005/jl0215.html).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FROM RUNTIME.EXEC() TO PROCESSBUILDER
Before JDK 5.0, the only way to fork off a process and execute
it local to the user runtime was to use the exec() method of the
java.lang.Runtime class. JDK 5.0 adds a new way of executing
a command in a separate process, through a class called
ProcessBuilder. You can find ProcessBuilder in the java.lang
package (like Runtime and Process). This tip discusses and
compares both approaches.
If you're familiar with the Runtime class, you know that it also
allows you to discover memory usage and add a shutdown hook. But
probably the most popular use of the class prior to 5.0 was to
execute a command in a separate process. This was done through
one of the six versions of the exec() method of Runtime:
public Process exec(String command)
throws IOException
public Process exec(String command,
String[] envp)
throws IOException
public Process exec(String command,
String[] envp,
File dir)
throws IOException
public Process exec(String[] cmdarray)
throws IOExceptionjava
public Process exec(String[] cmdarray,
String[] envp)
throws IOException
public Process exec(String[] cmdarray,
String[] envp,
File dir)
throws IOException
Before you call the exec() method, you specify the command and
its arguments, environment variable settings, and working
directory. All versions of the method return a java.lang.Process
object for managing the created process. This allows you to get
the input or output stream of the subprocess and exit status
(among other available information).
Here's an example, DoRuntime, that shows how to execute
a command with the original Runtime class. The command to run is

passed in from the command line.


import java.io.*;
import java.util.*;
public class DoRuntime {
public static void main(String args[]) throws IOException {
if (args.length <= 0) {
System.err.println("Need command to run");
System.exit(-1);
}
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(args);
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
System.out.printf("Output of running %s is:",
Arrays.toString(args));
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
}
If you run DoRuntime in Solaris like this:
java DoRuntime ls
You get output that looks something like this (which depends on
the contents of the directory):
Output of running ls is:DoRuntime.class
DoRuntime.java
Linux users could also pass in "ls" as the command to get
a directory listing.
On a Microsoft Windows platform, commands such as "dir" are
internal to the command processor so the single command-line
argument would be the quoted string: "cmd /c dir" (again, output
would depend on the contents of the directory).
> java DoRuntime "cmd /c dir"
Output of running cmd /c dir is: ...
Directory of C:\...
07/15/2005
07/15/2005
07/15/2005
07/15/2005
...

09:30
09:30
09:30
09:23

AM
AM
AM
AM

<DIR>
<DIR>

.
..
1,146 DoRuntime.class
724 DoRuntime.java

As coded, the command executes in the current working directory


with its environment variables intact.
If you want to run the command in a different directory, and you
need to add more arguments to the exec() command, you change:

Runtime runtime = Runtime.getRuntime();


Process process = runtime.exec(command);
to:
File file = new File(other directory);
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command, null, file);
The second parameter in the call to the exec() method identifies
the environment variable settings. Because the parameter is
"null", the subprocess inherits the environment settings of the
current process.
So what's wrong with this approach? Why create a new approach?
The problem is that the Runtime.exec approach doesn't necessarily
make it easy to customize and invoke subprocesses. The new
ProcessBuilder class simplifies things. Through various methods
in the class, you can easily modify the environment variables for
a process and start the process.
Here's a simple use of ProcessBuilder that duplicate the functions
of the DoRuntime example:
import java.io.*;
import java.util.*;
public class DoProcessBuilder {
public static void main(String args[]) throws IOException {
if (args.length <= 0) {
System.err.println("Need command to run");
System.exit(-1);
}
Process process = new ProcessBuilder(args).start();
InputStream is = process.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line;
System.out.printf("Output of running %s is:",
Arrays.toString(args));
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
}
> java DoProcessBuilder ls
Output of running ls is:DoProcessBuilder.class
DoProcessBuilder.java
DoRuntime.class
DoRuntime.java
Notice that the following two lines in DoRuntime:
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command);
were changed to the following line in DoProcessBuilder:
Process process = new ProcessBuilder(command).start();

The ProcessBuilder class has two constructors. One constructor


accepts a List for the command and its arguments. The other
constructor accepts a variable number of String arguments.
public ProcessBuilder(List<String> command)
public ProcessBuilder(String... command)
With ProcessBuilder, you call start() to execute the command.
Prior to calling start(), you can manipulate how the Process
will be created. If you want the process to start in a different
directory, you don't pass a File in as a command line argument.
Instead, you set the process builder's working directory by
passing the File to the directory() method:
public ProcessBuilder directory(File directory)
There isn't an obvious setter type method in ProcessBuilder for
setting environment variables. Instead, you get a Map of the
variables through the environment() method, then you manipulate
the Map:
ProcessBuilder processBuilder = new ProcessBuilder(command);
Map<String, String> env = processBuilder.environment();
// manipulate env
The options for manipulating the environment include adding
environment variables with the put() method, and removing them
with the remove() method. For example:
ProcessBuilder processBuilder = new ProcessBuilder(
command, arg1, arg2);
Map<String, String> env = processBuilder.environment();
env.put("var1", "value");
env.remove("var3");
After the environment variables and directory are set, call
start():
processBuilder.directory("Dir");
Process p = processBuilder.start();
You can also clear() all the variables from the environment and
explicitly set the ones you want.
With methods such as environment() for
environment variables from the process
starting a new process, ProcessBuilder
to invoke a subprocess with a modified

adding and removing


space, and start() for
should make it easier
process environment.

You can get the initial set of environment variables by calling


the getenv() method of System. Understand that not all platforms
support changing environment variables. If you try to change an
environment variable on a platform that forbids it, the
operation will throw either an UnsupportedOperationException or
an IllegalArgumentException. Also, when running with a security
manager, you'll need the RuntimePermission for "getenv.*",
otherwise a SecurityException will be thrown.
Remember not to forget the start() call after configuring your

instance. And, keep using the Process class to manipulate the


streams for the process and to get its exit status.
A word of caution about the examples in this tip. It is possible
that the examples will deadlock if the subprocess generates
enough output to overflow the system. A more robust solution
requires draining the process stdout and stderr in separate
threads.
For more information about ProcessBuilder, see the class
definition
(http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ProcessBuilder.html).
. . . . . . . . . . . . . . . . . . . . . . .
IMPORTANT: Please read our Terms of Use, Privacy, and Licensing
policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developers.sun.com/dispatcher.jsp?uid=6910008
* FEEDBACK
Comments? Please enter your feedback on the Tech Tips at:
http://developers.sun.com/contact/feedback.jsp?category=sdn
* SUBSCRIBE/UNSUBSCRIBE
Subscribe to other Java developer Tech Tips:
- Enterprise Java Technologies Tech Tips. Get tips on using
enterprise Java technologies and APIs, such as those in the
Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless
Java technologies and APIs, such as those in the Java 2
Platform, Micro Edition (J2ME).
To subscribe to these and other JDC publications:
- Go to the Sun Developer Network - Subscriptions page,
(https://softwarereg.sun.com/registration/developer/en_US/subscriptions),
choose the newsletters you want to subscribe to and click
"Submit".
- To unsubscribe, go to the Subscriptions page,
(https://softwarereg.sun.com/registration/developer/en_US/subscriptions),
uncheck the appropriate checkbox, and click "Submit".
- To use our one-click unsubscribe facility, see the link at
the end of this email:
- ARCHIVES
You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/developer/TechTips/index.html
- COPYRIGHT
Copyright 2005 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, California 95054 USA.
This document is protected by copyright. For more information, see:

http://java.sun.com/developer/copyright.html
Core Java Technologies Tech Tips
July 27, 2005
Trademark Information: http://www.sun.com/suntrademarks/
Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks
or registered trademarks of Sun Microsystems, Inc. in the
United States and other countries.

Вам также может понравиться