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 09:30 AM . 07/15/2005 09:30 AM .. 07/15/2005 09:30 AM 1,146 DoRuntime.class 07/15/2005 09:23 AM 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 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 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 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 adding and removing environment variables from the process space, and start() for starting a new process, ProcessBuilder should make it easier to invoke a subprocess with a modified 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.