One of the useful features in Java is the built-in support for writing multithreaded applications. A thread is an execution path in the program that has its own local variables, program counter, and lifetime. If the task being executed on the thread takes a long time, there needs to be a mechanism to stop, monitor, pause, and resume the task.
This article will take a nontrivial example with threads and refactor the code to include these capabilities. This article assumes a basic understanding of threads and some knowledge of SWING programming. A zip file is provided for download at the end.
Develop an application to search recursively for all Java source files containing a particular string token within a particular directory. Include a SWING user interface from which the user will be able to choose a root directory from the local machine and enter the string token to be searched. The user will have the ability to start, stop, monitor, pause, or resume the Search task. The final user interface should be like Figure 1.

Figure 1. Final user interface for the example
There are four buttons at the bottom, corresponding to searching, stopping, pausing, and resuming the task. A status label next to the Resume button displays the percentage of work done. The output files are displayed at the center. All fields on the screen should be enabled only when required. Note that we will solve this problem in three iterations as follows:
The simplest solution is to not use threads at all. Let's explore what the user will encounter if the above functionality is not implemented using threads. There will be only one button on the screen called Search. Upon clicking the button, the user will have to wait until all the files that contain the token are retrieved. To the user, this appears to be a severe performance issue, but because of the nature of the problem, the performance cannot be improved beyond a certain limit. So, the solution is to programmatically execute the search in such a way that the user can continue to do other things in the application. For example, if this functionality was embedded in an editor, the user should be able to edit files even though the search is going on in the background. Clearly, the solution is to use threads. However, this implementation can still be acceptable if the response time is short and the user is willing to wait.
In SWING, all parts of the user interface are drawn from an Event Dispatching Thread. Button clicks are captured as events, and we can write classes that "listen" to these clicks. In particular, for button clicks, any class that wants to listen to these events should implement an interface called ActionListener and implement method actionPerformed. Note that this method will be called from the Event Dispatching Thread. So, for a long-running task, the Event Dispatching Thread will be tied up and unavailable for other user interactions.
By executing the search functionality on a separate thread, the SWING event dispatching the thread will be free to do other tasks. However, using a thread raises certain questions:
The following user interface will work (see Figure 2).

Figure 2. User interface for the Search/Stop functionality
Initially the Search button, Directory button, and the Token field are enabled. When the user clicks on the Search button, these fields are disabled (this prevents the user from clicking the button again). At this point the Cancel button is enabled and the status field shows a "Working ..." text. The status field has been introduced to let the user know that the search is still in progress. Let's try to capture all the classes needed for this to work. Clearly, we require three classes:
|
A class model in Figure 3 illustrates the design.

Figure 3. Class diagram for Search/Stop functionality
The FileFinder.java class is responsible for finding all files under the root directory and contains the string token. The constructor takes in the File object for the root directory. The findFiles(..) method returns the list of File objects. The usage of the FileFinder class looks as follows:
FileFinder finder = new FileFinder(dirFile);
List javaFiles = finder.findFiles(token);
We are not going to spend time looking at the algorithm. I would encourage you to look at the complete source for each iteration before proceeding with the next iteration. SearchForm.java SearchForm class will present a swing interface to the user and display the output when the user clicks on the Search button. The constructor starts by setting the size of the window. It adds all the SWING components to the JFrame contentPane. To handle the button clicks, SearchForm implements an ActionListener interface. Listeners are a common mechanism used in SWING to handle the user actions from the screen. The SearchForm.actionPerformed(..) method implements the functionality when either the Directory, Search, or Cancel buttons are clicked. When the Directory button is clicked, a JFileChooser is displayed where a directory can be chosen. See the zip file for the actual code. When the Search button is clicked, a separate thread should be invoked to kick off the search process using the SearchThread class. Here is a snippet of the class:
public class SearchForm extends JFrame
implements ActionListener {
private JButton dirButton
= new JButton("Choose Directory");
private JButton searchButton
= new JButton("Search");
private JTextArea area
= new JTextArea();
private JTextField tokenField
= new JTextField("");
public SearchForm() {
// set the initial size
setSize(600, 300);
// Exit the application on window close
this.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
((JPanel) getContentPane()).setBorder(
BorderFactory.createEmptyBorder(
5, 5, 5, 5));
....
// add the panels to the frame
getContentPane().add("North",
northPanel);
getContentPane().add("Center",
new JScrollPane(area));
getContentPane().add("South",
southPanel);
}
public static void main(String[] args) {
SearchForm form = new SearchForm();
form.setVisible(true);
}
/**
* Act on the button click from the user
*/
public void actionPerformed(ActionEvent e){
// open a file dialog and let the
// user choose a file
Object source = e.getSource();
if (source == dirButton) {
....
} else if (source == searchButton) {
....
}
}
public void setTextArea(List javaFiles) {
....
}
}
SearchThread.java: To create a new thread for search, we need to extend the Thread class. A new class, called the SearchThread, is created as follows:
public class SearchThread extends Thread {
private File rootDir;
private String token;
private SearchForm form;
public SearchThread(File rootDir,
String token, SearchForm form) {
this.rootDir = rootDir;
this.token = token;
this.form = form;
}
public void run() {
FileFinder finder
= new FileFinder(rootDir);
List javaFiles
= finder.findFiles(token);
form.setTextArea(javaFiles);
}
}
The constructor takes in the root directory, the token, and the SearchForm itself (so that the output can be displayed on the screen). Note that the FileFinder class is now used from the SearchThread. The thread itself is invoked when the user clicks on the Search button as follows:
// Invoke the search on a different Thread
File dirFile = new File(dirName);
String token = tokenField.getText();
sThread
= new SearchThread(dirFile,token,this);
sThread.start();
Note that the variable sThread is now a member variable of the class SearchForm. Once the thread is started, the SWING event dispatching the thread is free to do other operations. One important operation that the user can do after starting a search is to stop the search. Note that the search thread calls the method:
form.setTextArea(javaFiles);
to show the files on the text area in the middle of the screen. One of the golden rules of SWING is that, all screen changes (almost) need to be done from the SWING event dispatching thread. However, here we seem to be doing it from the search thread. Let's see the implementation of the form.setTextArea(..):
public void setTextArea(List javaFiles) {
StringBuffer areaBuffer
= new StringBuffer();
Iterator fileIter
= javaFiles.iterator();
while (fileIter.hasNext()) {
File file
= (File) fileIter.next();
areaBuffer.append(
file.getAbsolutePath())
.append('\n');
}
if ("".equals(areaBuffer.toString())){
areaBuffer.append(
"No Files Found !!!");
}
SwingUtilities.invokeLater(
new SetAreaRunner(area,
areaBuffer.toString()));
}
|
The SWING pros will immediately notice that the SwingUtilities.invokeLater solves our problem. The method setTextArea(..) first creates a string from the list of files and then uses invokeLater(..) to set the text area field on the screen. By wrapping the code in SwingUtilities.invokeLater(..), we effectively call the code from the event dispatcher thread. The invokeLater method simply queues the Runnable object in the event dispatcher queue. The SWING event dispatcher thread detects the message and runs the code in the run() method. Figure 4 shows a sequence diagram for what happens when a user clicks on the Search button.

Figure 4. Sequence of events when the user clicks on the Search button
Now that we have started a thread, how do we stop it? Java provides us with a particularly important feature to interrupt the thread. The syntax is:
thread.interrupt();
The interrupted thread needs to periodically check whether the thread has been interrupted. If so, the thread needs to break out of any for(..) or while(..) loop and exit from the time-consuming task. Note that this code needs to be inserted by the developer and is not automatically available. The JDK does provide a stop() method on the thread class. However, this method has been deprecated and is dangerous to use. In our code, the implementation is simple. The following is done when the user clicks on the Cancel button:
if (sThread != null) {
sThread.interrupt();
}
Note that the SearchThread class simply delegates all of the responsibility of searching to the FileFinder class. So, somehow the FileFinder class needs to know that an interrupt has been sent to it. Any thread can check whether it has been interrupted by using the following:
Thread.currentThread().isInterrupted()
If it returns true, the thread was interrupted. So, the SearchThread would have to periodically check whether it was interrupted and, if so, exit. Note that there must be a balance between the liveliness of a thread and the performance degradation that may occur due to too many checks for interrupt; too many checks would improve the liveliness of the thread, since it can respond to the interrupt faster. In our implementation, the check is done before every file is searched. Figure 5 shows a sequence diagram for what happens when a user clicks on the Stop button.

Figure 5. Sequence of events when the user clicks on the Stop button
The implementation still has some glaring issues:
In this iteration, we will add the capability to show the search progress. The interface will not look much different (see Figure 6).

Figure 6. User interface for monitoring task
When the Search button is clicked, the Cancel button will be enabled. Also, the results will be displayed when available (rather than at the end), and the status field will show the percentage completed as above. To implement the above functionalities, we will have to refactor the FileFinder code so that the search thread is aware of the percentage of the task. The current functionality (FileFinder) does not calculate upfront how much of the work needs to be done. So, let us create a new class called TokenSearchWork, which will have two methods:
getAllDirectories(): Determines which directories need to be searched. This will tell the search thread the scope of the task to be performed. It effectively divides the task into multiple subtasks.findFilesInDirectory(): Searches for a token in a specified directory.
|
Here is the outline of the TokenSearchWork class:
public class TokenSearchWork {
private File rootDir;
public TokenSearchWork(File rootDir) {
this.rootDir = rootDir;
}
public List getAllDirectories()
throws InterruptedException {
return findDirs(rootDir);
}
private List findDirs(File directory)
throws InterruptedException {
checkForInterrupt();
....
return foundDirs;
}
....
private void checkForInterrupt()
throws InterruptedException {
if (Thread.currentThread()
.isInterrupted()) {
throw new InterruptedException(
"Interrupted !!!");
}
}
}
Note the use of InterruptedException: it is thrown by both methods. This is useful for stopping the thread. We create a private method called checkForInterrupt(), which simply throws an InterruptedException whenever it detects that an interrupt has occurred. This simplifies the implementation to a certain extent. This exception will be caught by the search thread and handled correctly. The run method of SearchThread will now change to use the new TokenSearchWork class as follows:
public void run() {
List allFiles = new ArrayList();
TokenSearchWork work
= new TokenSearchWork(rootDir);
int percentDone = 0;
try {
List allDirs
= work.getAllDirectories();
int sizeWork = allDirs.size();
for (int j = 0;
j < allDirs.size(); j++) {
File directory
= (File) allDirs.get(j);
allFiles.addAll(
work.findFilesInDirectory(
directory, token));
percentDone
= 100 * (j + 1) / sizeWork;
form.setTextArea(allFiles,
percentDone, false);
}
} catch (InterruptedException intExp) {
// The Task was interrupted
}
form.setTextArea(allFiles,
percentDone, true);
}
The run() method first constructs the TokenSearchWork class. It then calls getAllDirectories() method to get the list of directories. This informs the thread of the amount of total work that needs to be done. After that, for each directory, the thread searches for the token inside the files by calling findFilesInDirectory(..). By doing this, the search thread is always aware of what percentage of the work has been completed. Also, catching InterruptedException helps to exit from the for(..) loop. Note the change in the parameters of the SearchForm.setTextArea() to accommodate the percentage complete and the cancellation. Two new parameters have been introduced: percentDone and a boolean to see whether the task is done. SearchForm handles this change as follows:
public void setTextArea(List javaFiles,
int percentDone, boolean done) {
....
SwingUtilities.invokeLater(
new SetAreaRunner(area, areaBuffer
.toString(), percentDone, done));
}
private class SetAreaRunner
implements Runnable {
private JTextArea area;
private String text;
private int percent;
private boolean done;
public SetAreaRunner(JTextArea area,
String text, int percent,
boolean done) {
this.area = area;
this.text = text;
this.percent = percent;
this.done = done;
}
public void run() {
// Set the UI fields correctly
}
}
The functionality in this iteration is good enough for most scenarios. However, there may be occasions where we need two more functionalities: pause() and resume(). Just like stop(), Java provides these methods on the Thread class. However, the functionalities have been deprecated and are dangerous to use. So, if we need them, we will have to create our own pause() and resume().
In this iteration, we will implement the pause and resume functionality. Two new buttons are added as follows; the screen looks like Figure 7 when the Pause button is clicked.

Figure 7. User interface for Pause/Resume
To implement the pause and resume functionality, we should somehow be able to make the thread "sleep" when the Pause button is clicked. Also, we should be able to "wake up" the thread by clicking on the Resume button. The best way to do this in Java is to use the wait(..) and notify(...) method. We will introduce a new member variable called request in SearchThread. This variable will have one of the three values.
private static final int NORMAL = 0;
private static final int PAUSE = 1;
private static final int RESUME = 2;
Two new methods for pause and resume will have to be added to the SearchThread as follows (note the use of the synchronized keyword):
public synchronized void pauseWork() {
request = PAUSE;
notify();
}
public synchronized void resumeWork() {
if (request == PAUSE) {
request = RESUME;
notify();
}
}
The two methods above will set the request member variable appropriately. In addition, resumeWork() also calls the notify(..) method to wake up the SearchThread that is waiting. To make the thread go to sleep, a new method called waitIfPauseRequest is added:
private void waitIfPauseRequest()
throws InterruptedException {
synchronized (this) {
if (request == PAUSE) {
while (request != RESUME) {
wait();
}
request = NORMAL;
}
}
}
As seen above, the thread will invoke the wait(..) method when the request variable is set to PAUSE. Until the request is set to RESUME, the thread will sleep. The only exception is when the thread is interrupted. In such a case, an InterruptedException is thrown by the wait(..) method. This happens when the user clicks on the Cancel button, and the result is to exit the thread. The functionality is exactly what we need; the front end will simply call these methods when the user clicks on the Pause or Resume button as follows (note that the buttons are appropriately enabled/disabled):
....
} else if (source == pauseButton) {
if (sThread != null) {
sThread.pauseWork();
pauseButton.setEnabled(false);
resumeButton.setEnabled(true);
}
} else if (source == resumeButton) {
if (sThread != null) {
sThread.resumeWork();
pauseButton.setEnabled(true);
resumeButton.setEnabled(false);
}
}
....
We have created a user-friendly search functionality with the ability to start, monitor, pause, resume, and stop. One fact to note about pause and resume capabilities is that they should not be implemented for a task running in a database transaction. Pausing such a task would simply increase the transaction time and other threads will appear to hang. Apart from other glaring problems like transaction timeouts, the performance will degrade substantially.
Even though the stop(), pause(), and resume() methods have been deprecated, we can develop these if we are sufficiently careful. The article provides a simple way to handle these functionalities in a Java program. However, the SearchThread is very coupled with the front end and the actual search task. Looking at it closely, we see that every time-consuming task whose progressed can be measured (search being one example), should be able to use the same threading functionality available in the SearchThread. For better reuse, you can create a generic framework to handle stop, monitor, pause, and resume. If you're especially motivated, you can also experiment with providing these functionalities in a multi-user enterprise environment (using say Ajax and servlets).
Viraj Shetty is a J2EE Architect with 8 years of experience in the Enterprise J2EE Technology. He holds a Masters degree in Computer Science from Johns Hopkins University.
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.