Controlling Threads by Exampleby Viraj Shetty
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.
A Nontrivial Example
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:
- Using a thread with stop functionality
- Determining the thread progress
- Adding the pause and resume functionality
User Interface and Threads
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.
Using a Thread with Stop Functionality
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:
- How do we display the results from this separate thread on the SWING screen?
- How do we stop the user from clicking a button twice?
- How do we stop the thread?
- How do we know that the thread is still working?
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:
- FileFinder: Does the actual job of searching.
- SearchForm: Takes care displaying the results using SWING and acts as a View.
- SearchThread: Implements the functionality for threads.