Mudra Services
20749 Bridalveil Falls Terrace
Potomac Falls, VA 20165
(571) 243-5888   |   FAX : 1 (866) 8672363
Email :
Product Development

Useful Java Links

Controlling Threads
Author - Viraj Shetty

1. Introduction

One of the useful features in java is the built in support for writing multi-threaded applications. A thread, sometimes referred to as a lightweight process, is an execution path in the program which has it's own local variables, program counter and lifetime. If the task which is being executed on the thread takes a long time, then there needs to be a mechanism to
  • Monitor the progress of the task
  • Pause the execution of the task
  • Resume a paused task
  • Cancel the execution of the task
This article will take a non-trivial example without using threads and re-factor the code to include monitor, pause, resume and cancel capabilities. Finally, we will re-factor the code to develop a generic thread code to handle all these capabilities. This article assumes a basic understanding of threads and knowledge of SWING programming.

2. The problem

Develop an application to search for all java source files, which contains a particular string token - within a particular directory recursively. Include a swing user interface from which the user will have the capability 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 look as follows.

As seen in the figure, there are two fields called * Root Directory, which the user chooses from a JFileChooser * Token, which the user enters There are four buttons at the bottom, which correspond to searching, canceling, 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 4 iterations and then re-factor the code to develop a generic worker thread, which handles pause, resume, stop and progress. The four iterations are as follows

  • Simplest solution (No User Threads)
  • Adding a Thread with stop functionality
  • Determining the thread progress
  • Adding the pause and resume functionality

3. Simplest Solution (No User Threads)

The simplest solution is to not use threads at all. There will be only one button on the screen called Search. The most common sense approach to solving this problem is to separate the display issues from the problem of finding the files. So, we will have two source files
  • FileFinder.java, which returns the list of java files when the root directory and the token is provided.
  • SearchForm.java, which takes care of the display using SWING.
The screen will look as follows.

FileFinder.java

This class is responsible for finding all files under the root directory and which contains the string token. The constructor takes in the File object for the root directory. The findFiles(..) method returns the list of File objects.

public class FileFinder { private File dir; public FileFinder(File dir) { this.dir = dir; } public List findFiles(String token) { return findFilesRecursively(dir, token); } ... }

The usage of the FileFinder class would look 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. Click here to see the complete source file for FileFinder.java.

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. In order to handle the button clicks, SearchForm implements an ActionListener interface. Listeners are common mechanism in SWING to handle the user actions from the screen.

The SearchForm.actionPerformed(..) method implements the functionality when either the Search or the Directory button is clicked. When the directory button is clicked, then a JFileChooser is displayed where a directory can be chosen as below

JFileChooser chooser = new JFileChooser(); chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int value = chooser.showOpenDialog(this); if (value == JFileChooser.APPROVE_OPTION) { File file = chooser.getSelectedFile(); dirButton.setText(file.getAbsolutePath()); }

Note that the text displayed on the button changes when a directory is selected from the JfileChooser (This may not be the best implementation, but suffices)

When the Search button is clicked, the search needs to be invoked. In other words, the FileFinder object needs to be created and used as below.

String dirName = dirButton.getText(); if (!"Choose Directory".equals(dirName)) { File dirFile = new File(dirName); FileFinder finder = new FileFinder(dirFile); List javaFiles = finder.findFiles(tokenField.getText()); setTextArea(area, javaFiles); } }

This code makes sure that a directory is chosen before the search button is clicked. It uses the FileFinder object to find the files. The setTextArea(..) method is called to populate the JTextArea with the results. A snippet of the class is shown below.

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) { .... } } private void setTextArea(JTextArea area, List javaFiles) { .... } }

Click here to see the complete source file for SearchForm.java.

Note that by separating the two classes, the view is separated from the model. This implementation works if the response time is small and the user is willing to wait. But if the directory contains many sub-directories and many java files, the amount of time taken to respond to a search request will be too much for the user to bear. Particularly, if this functionality was embedded in an editor, then the user would not be able to edit files if the application forces the user to wait till the search is completed.

Click here to see the complete list of source files for this iteration.

4. Adding a Thread with stop functionality

The solution to the above problem is clearly to use threads. By executing the search functionality on a separate thread, the SWING event dispatching thread is now free to do other tasks, which the user may invoke. However, the introduction of a thread in the mix raises certain questions.
  • How do we display the results from this separate thread on the SWING screen?
  • Once the search button is clicked, the user can choose to click again. This can quickly lead to a problem.
  • How do we cancel the thread?
  • How do we know that the thread is still working?
Let's go ahead and improve our interface as follows.

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. To create a new thread for search, we need to extend the Thread class. A new class is created called the SearchThread 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 thread is free to do other operations. An important operation that the user can do after starting a search is to cancel 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) needs 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())); } private class SetAreaRunner implements Runnable { private JTextArea area; private String text; public SetAreaRunner(JTextArea area, String text) { this.area = area; this.text = text; } public void run() { area.setText(text); searchButton.setEnabled(true); cancelButton.setEnabled(false); dirButton.setEnabled(true); tokenField.setEnabled(true); statusLabel.setText(""); sThread = null; } }

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.

Now that we have effectively redrawn the screen, we need a good java implementation to cancel a thread. Java provides us with a particularly important feature to interrupt the thread. The syntax for that is

thread.interrupt();

The interrupted thread needs to periodically check if 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 put in by the developer and is not automatically available.

Note that java does provide a stop() method on the thread. However, this method has been deprecated and is dangerous to use. The reason for this is that the JVM will simply throw a ThreadDeath error at the line of execution, effectively coming out of the thread. The problem here is that if the data being manipulated is critical (maybe a linked list), there is a high probability that we end up with corrupt data.

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 know that an interrupt has been sent to it. Any thread can check if 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 has to be a balance between 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 after entering a new directory. Click here to see the complete list of source files for this iteration.

The implementation still has some glaring issues.

  • The user still has to wait till the end to see if there are any results
  • There is no indication of the progress of the task. The user only sees "Working ..." but does not know how much is done.

5. Determining the thread progress

In this iteration, we will add the ability to show the progress of the search. The interface will not look much different.

On clicking on search, the cancel button will be enabled. Also, the results will be displayed as and when it is detected (rather than at the end) and status field will show the percent complete as above.

To implement the above functionalities, we will have to refactor the FileFinder code so that search thread is aware of the percentage of the task. The current functionality does not calculate how much of the work needs to be done.

So, let us create a new class called TokenSearchWork, which will have two methods.

  • getAllDirectories(), to find out what directories need to be searched. This will give the search thread, the scope of the task to be performed. It effectively divides the task into multiple sub tasks.
  • findFilesInDirectory(), to search for a token in a specified directory.
The outline of the TokenSearchWork class is as shown below.
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. Both methods throw InterruptedException. This is useful for canceling 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 cancel 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 all the directories recursively. After that, for each directory, the thread would search for the token in files inside by calling findFilesInDirectory(..). By doing this, the Search thread is always aware of what percentage of the work has been done. Also, InterruptedException is caught but nothing is done (we can at least log it). However, it helps to exit from the for(..) loop.

Note the change in parameters of the form.setTextArea() to accommodate the percentage done and the cancellation. Two new parameters have been introduced. Percentage done and boolean to see if 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() { area.setText(text); statusLabel.setText(" Working ... " + percent + "%"); if (done) { searchButton.setEnabled(true); cancelButton.setEnabled(false); dirButton.setEnabled(true); tokenField.setEnabled(true); if (percent == 100) statusLabel.setText(""); else statusLabel.setText(" Cancelled at " + percent + "%"); sThread = null; } } }

Click here to see the complete list of source files for this iteration.

The functionality in this iteration is good enough for most scenarios. However, there may be several occasions where we may 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 is dangerous to use. So, if we need these functionalities, we will have to create our own pause() and resume().

6. Pause and Resume the Thread

In this iteration, we will implement the pause and resume functionality. Two new buttons are added as follows. The screen would look like below when the pause button is clicked.

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 "wakeup" the SearchThread 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 3 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 above two methods will set the request member variable appropriately. In addition, the resumeWork() also calls the notify(..) method to wake up the SearchThread which is waiting.

So, how does the search thread go to "sleep" when the pauseWork(..) is called on the search thread.

To make the thread go to sleep, and a new method called waitIfPauseRequest.

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 will happen when the user clicks on the cancel button and the result would be to exit from 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); } } ....

Click here to see the complete list of source files for this iteration.

We have created a user-friendly search functionality with ability to start, monitor, pause, resume and stop. However, from the perspective of implementation, 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, which can measure it's progress (search being one example), should be able to use the same threading functionality available in the SearchThread. So, would it be possible to create a generic version of the thread which can be used by any View and any long running task? The answer to this is yes, provided the work class following some simple rules.

7. Generalize the Stop, Monitor, Pause and Resume

In this iteration, we will tackle the issue of creating a generic worker thread, which automatically takes care of pause, resume, progress and cancel. There are no changes to the View in this section.

If we take a look at the SearchThread code, you will see that this class has the knowledge of the SearchForm as well as the TokenSearchWork (the task). We would now proceed to refactor the code so that a generic version of the thread would suffice in most cases to get the stop, monitor, pause and resume functionality that's needed.

First, let's try to make the search thread independent of the TokenSearchWork class. In java, interfaces are a good way to conceptualize an idea. So, conceptually speaking, an asynchronous activity has three parts to it

  • A time consuming Task (we will call this Work), which has no knowledge of threads or display. It's primary focus is to provide the mechanism to execute the task. It also helps in subdividing the task into multiple smaller pieces of work.
  • A Thread (we will call this a WorkerThread), which has no knowledge of what work is being done and how the results are displayed on the View. It's primary focus is to control the execution of the work.
  • A View (which can implement a WorkListener), which displays the UI for manipulating the Work.
We will start with creating a new interface called Work as follows.
public interface Work { public static class WorkKey { private int ID; private int percent; public WorkKey(int ID, int percent) { this.ID = ID; this.percent = percent; } public int getID() { return ID; } public void setID(int id) { ID = id; } public int getPercent() { return percent; } public void setPercent(int percent) { this.percent = percent; } } List scopeWork() throws InterruptedException; Object doWork(WorkKey key) throws InterruptedException; }

This interface defines two methods.

  • scopeWork() - Used for scoping the work which means to find everything that needs to be done. This method will effectively divide one long-running task into many small tasks. It returns a list of WorkKey objects. Each WorkKey object represents a key to a smaller unit of work. It should throw an exception if the thread was interrupted.
  • doWork() - Used for doing the work associated with the WorkKey object. It should throw an exception if the thread was interrupted. This method will return the results of the work. Since, different tasks will have different results, we identify the class as simply 'Object'.
The TokenSearchWork will now be implementing the Work interface. Loosely speaking, scopeWork() maps to getAllDirectories() and doWork() maps to findFilesInDirectory(..) method. Click here to see the implementation.

Also, to remove the SearchForm dependency on the WorkerThread, we introduce a new interface called WorkListener. This interface has one method as follows. The output here is a list of objects representing the output of the doWork(..) for work associated with the WorkKey keys which are completed. The WorkerThread acts as the glue between the View and the Work, effectively isolating the two.

public interface WorkListener { void handleProgress(List output, int percent, boolean done); }

The SearchThread (now renamed as the WorkerThread) will be constructed with -

public WorkerThread(Work work,WorkListener listener) { this.work = work; this.listener = listener; }

and the run method will work as follows.

public void run() { List allOutput = new ArrayList(); int percentDone = 0; try { List keys = work.scopeWork(); int sizeWork = keys.size(); for (int j = 0; j < keys.size(); j++) { waitIfPauseRequest(); WorkKey wKey = (WorkKey) keys.get(j); allOutput.add(work.doWork(wKey)); percentDone = wKey.getPercent(); listener.handleProgress(allOutput, percentDone, false); } } catch (InterruptedException intExp) { // The Task was interrupted } listener.handleProgress(allOutput, percentDone, true); }

Thus, now the WorkerThread is fully generic and has no dependencies to the SearchForm or the TokenSearchWork class.

Click here to see the complete list of source files for this iteration.

Now, let's assume that we want to create a front end for another long running task. Let's say we want to import large amount of XML data into a database table. We create a new Work class called ImportDataWork as follows

Class ImportDataWork implements Work { .... }

We create an ImportDataView, which can be implemented as follows.

Class ImportDataView implements WorkListener { .... }

At the point of invoking the import, we follow the following rules.

ImportDataWork work = new ImportDataWork(...); WorkListener workListener = ... ; WorkerThread thread = new WorkerThread(work,workListener); thread.start();

8. Note on pause and resume

One fact to note about pause and resume capabilities is that it should not be implemented for a task running under a single database transaction. Pausing such a task would simply increase the transaction time and other threads will appear to hang.

9. Conclusion

Even though the stop(), pause() and resume() methods have been deprecated, we can develop these if we are sufficiently careful. We have also created a generic worker thread, which takes care of these functionalities as long as the work to be done implements the Work interface.

10. Downloads

Click here to get the zip file of all the iterations and play with them.