Sign In/My Account | View Cart  

advertisement

AddThis Social Bookmark Button

Listen Print Discuss

Controlling Threads by Example
Pages: 1, 2, 3, 4

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().

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 looks like Figure 7 when the Pause button is clicked.

Figure 7.
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.

Conclusion

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).

Resources

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.