[ZT] 怎样停止一个线程或者任务

source file http://forward.com.au/javaProgramming/HowToStopAThread.html

Abstract

How to stop a Thread is a perannual question for Java programmers. Finally with the release of Java V5.0 (or V1.5), which incorporates java.util.concurrent, a definitive answer can be given. The answer is you stop a thread by using interrupt(). This article covers some of the background to stopping threads and the suggested alternatives and discusses why interrupt() is the answer. It also discusses interrupting blocking I/O and what else the programmer needs to do to ensure their threads and tasks are stoppable.
Introduction

One of Java's claims to fame is that it was conceived from the start as a language that would support multi-threaded programming. Threads are a first class object and the synchronized keyword was provided to assist in co-ordinating threads. Unfortunately as one of the first (the first?) languages to incorporate threads into its design, Java's designers did not get everything right the first time round.

In particular, they failed in their attempts to provide a safe and effective means of stopping a thread once it had started. The original java.lang.Thread class included the methods, start(), stop(), stop(Throwable) and suspend(), destroy() and resume(), which were intended to provide the basic functionality for starting and stopping a thread. Of these only the start() method has not been depreciated. The javadoc comments for the other methods warn against their use.

The Sun article, Why are Thread.stop, Thread.suspend and Thread.resume Deprecated? , goes into some detail as to why these methods were deprecated (Note: destroy() has now also been depreciated). Some of that discussion will be covered below.

The problems with stopping Threads were not the only problems the previous versions of Java had. There was also no straight forward mechanism to catch and handle any exception the thread might throw out of its run method. This lead to poor coding like

public void run() {
   // note the Thread.run() method is declared
   // NOT to throw any checked exceptions
   try {
      ....
     // some code here which might throw
     // a checked exception such as an IOException
     ..
   } catch (IOException ioex) {
      ioex.printStackTrace(); 
   }
}

because it was not easy to let the rest of the program know that the thread had terminated with an exception. A related problem was that of returning the results of a thread's action. Typically these results had to be stored in some synchronized variable and then the thread had to notify the caller when the results were valid. For Java V1.4 and below, my ThreadReturn package provided a solution to both these problems.

Other problems with multi-threaded programming in previous versions of Java include a lack of common robust utilities to do such things as,

    *

      synchronize two threads at a point and exchange objects
    *

      a counting semaphore to control/restrict concurrent access to a resource (synchronized limits access to just one thread at a time)
    *

      a countdown latch that blocks one or more threads until a set of operations performed by other threads completes
    *

      read/write locks which allow any number of thread to read but only one thread to write.
    *

      atomic updates for Integer, Long etc which allow a value to be set in a thread safe lock-free manner.

Java V5.0 incorporates Doug Lee's Concurrent package as java.util.concurrent . This package provides robust utilities for the above items. You are encouraged to read the javadocs carefully and make use of these utilities. Since this article is primarily about stopping Threads it will not go into any more detail on these utilities.
Suggested Methods for Stopping a Thread.

Now that the Thread's stop(), suspend() etc., methods have been deprecated, the only way to safely terminate a thread is to have it exit its run() method, perhaps via an un-checked exception. In Sun's article, Why are Thread.stop, Thread.suspend and Thread.resume Deprecated? , there are some suggestions for how to stop a thread without using the unsafe deprecated methods.

The method suggested there is to use a volatile stop flag (blinker in the code below)

    private volatile Thread blinker;
    public void stop() {
        blinker = null;
    }
    public void run() {
        Thread thisThread = Thread.currentThread();
        while (blinker == thisThread) {
            try {
                thisThread.sleep(interval);
            } catch (InterruptedException e){
            }
            repaint();
        }
    }

The volatile keyword is used to ensure prompt communication between threads. “A field may be declared volatile, in which case a thread must reconcile its working copy of the field with the master copy every time it accesses the variable. Moreover, operations on the master copies of one or more volatile variables on behalf of a thread are performed by the main memory in exactly the order that the thread requested.” (See http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#36930 for more details.
Synchronization, Volatile and Atomic Variables

In general, multi-threaded access to non-atomic variables should be synchronized to avoid invalid values being retrieved. Atomic variables are guaranteed to be updated (saved or loaded) in one machine instruction and so are always in a valid state regardless of the number of threads accessing them. Note however due to caching the valid state may not be the most up todate one. What are atomic variables varies from architecture to architecture (32 versus 64 bit for example) . In general primitive data types with sizes smaller then the machine word length are atomic. On most (all) architectures these include, char, byte, short, int, boolean.

However “The load, store, read, and write actions on volatile variables are atomic, even if the type of the variable is double or long. “ http://java.sun.com/docs/books/jls/second_edition/html/memory.doc.html#28733

So declaring the Thread variable, blinker, as volatile makes its update atomic and hence synchronization is not needed for this variable. Also marking this variable as volatile ensures the most up todate value is always used.
Non-Runnable Thread States

In order for the thread to respond to the stop flag the thread has to be running. A thread is in a non-runnable state if

    *

      Its sleep method is invoked.
    *

      The thread calls the wait method to wait for a specific condition to be satisfied.
    *

      The thread is blocking on I/O.

If thread is in a non-runnable state, setting the stop flag variable will have no effect. In the above example, after calling the stop() method above, you have to wait until the sleep interval has expired before the thread stops. To get the thread to stop promptly you need to break out of the sleep using interrupt(). (Interrupt() will also break out a wait method.)

public void stop() {
        Thread tmpBlinker = blinker;
        blinker = null;
        if (tmpBlinker != null) {
           tmpBlinker.interrupt();
        }
    }

Note: tmpBlinker does not need to be declared volatile because the load from volatile blinker is guaranteed to be atomic.

To summarize, now that the Thread stop() method has been found to be unsafe and has been depreciated, Sun suggests its functionality be replaced with a stop variable. However this by itself is not sufficient to stop the thread promptly. You also need make sure that the thread runnable. At the very least this requires a call to the Thread interrupt() method to break out of sleep() and wait() methods that the thread may be trapped in, in some lower level method. Breaking out of the third non-runnable state, blocking on I/O is more involved and will be discussed below.
IfInterruptedStop()

In the ThreadReturn package I combined the need for a stop variable and the need to call interrupt() into one. As discussed above the only safe way to stop a thread is to exit its run() method. In order to do that the thread needs to be running, so when trying to stop a thread you would normally call its interrupt() method as part of the stopping process.

Now when a thread is in a sleep() or wait() method, calling interrupt() on that thread breaks out of the sleep or wait and throws an InterruptedException. Ensuring this exception propagates all the way back up to the exit of the run() method is the simplest means of stopping a thread. Since the run() method is not declared to throw any checked exceptions, the InterruptedException needs to be caught and wrapped in an un-checked exception (such as a RuntimeException). Java will insist you catch and handle the InterruptedException because it is a checked exception. So code your handler like so

try {
   ....
   wait();
} catch (InterruptedException iex) {
   throw new RuntimeException("Interrupted",iex);
}

You may like to define your own un-checked exception class to use for this purpose so you can distinguish it from other RuntimeExceptions. In Java V1.5 there is a java.util.concurrent.CancellationException you can use, although it does not have a constructor that takes a throwable which limits is usefulness. You are probably better off defining your own.

Then provided you do not catch the un-checked exception at some intermediate level, it will ensure the thread stops by exiting the run() method. There may be some cases where you want to interrupt a thread's wait() or sleep() method when you don't want to stop the thread. In these cases you should put a try/catch block around the wait() or sleep() and catch and handle the InterruptedException there without propagating an exception back up to the top run() method.

Now if the thread has started, and was not in a sleep() or wait() method, then calling interrupt() on it does not throw an exception. Instead it sets the thread's interrupted flag. This flag can then be used as the thread's stop flag. To do this insert the following code fragment in those places you want to check for stopping the thread. (See the note below about stopping threads that have not yet started.)

    Thread.yield(); // let another thread have some time perhaps to stop this one.
    if (Thread.currentThread().isInterrupted()) {
      throw new InterruptedException("Stopped by ifInterruptedStop()");
    }

The call isInterrupted() does not clear the thread's interrupted flag. You should avoid using the interrupted() method (not to be confused with interrupt()) because a call to interrupted() clears the thread's interrupted flag and will prevent you detecting the request to stop.

The static methods ThreadReturn.ifInterruptedStop(), FutureTalker.ifInterrruptedStop() and TaskUtilities.ifInterruptedStop() all contain this code. FutureTalker requires Java 1.5 while the other two only need Java 1.4 or higher. So you can inserting the following line at strategic points in the thread in place of the previous code

  TaskUtilities.ifInterrruptedStop();

Why interrupt() is the answer.

In the preceding section I have shown how you can stop your thread by only using interrupt(). The question to be answered now is why this is the preferred method of stopping threads. The answer lies in the code of the new Java V1.5 standard library java.util.concurrent. This standard library provides a unified means of passing Tasks to threads and retrieving the results, or the error if one occurred. This standard library, together with my FutureTalker package provides Java V1.5 with the functionality my ThreadReturn package provided for Java V1.4.
Multi-Core Hardware

Before delving into the code, lets examine what java.util.concurrent means for multi-threaded programming. In java.util.concurrent Threads are abstracted to Tasks. When using Threads directly, you override the run() method to do something useful and then create the Thread object and call start() which initializes the thread and calls the run() method. This thread initialization is not cheep. Allen Holub (Taming Java Threads, pp209) reports it takes 15 times longer to start the thread then it does to create it. Also the number of threads can be a limited resource in some systems.

With this in mind you can see the advantage of separating the task to be run from the thread running it and using a pool of reusable threads to run the tasks. This functionality is provided by java.util.concurrent. Instead on concentrating on Threads and their run() methods, java.util.concurrent instead talks about tasks which implement Callable<V>. The Callable<V> interface defines just one method,

  V call() throws Exception

which computes a result, or throws an exception if unable to do so. This is a noticeable improvement over the Thread's run() method because call() can return a result and throw a checked exception. Building on the Callable interface, java.util.concurrent provides classes for the asynchronous execution of Callable tasks. The Future<V> interface represents the result of an asynchronous task. FutureTalker is a concrete implementation of this interface which provides the ability to add listeners which will be informed when the task terminates.

In a recent Scientific American article, “A Split at the Core” (Nov. 2004, Vol 291, No 5), Gibbs reported the move to multi-core processors which provides multiple processors in a single package. To take advantage of such hardware, programs will need to run as many tasks in parallel as possible. This is where java.util.concurrent and the Callable interface come into their own. By separating the task from the thread that runs it and by reducing the overhead of starting multiple tasks, the programmer can make many more operations into tasks and even break a task in to sub-tasks, leaving the thread pool factory class as the single point of configuration to efficiently allocate these tasks to utilize the available processing power. Apart from the fact that java.util.concurrent is now a standard Java library, the move to multi-processor hardware should be enough to convince you that programming tasks based on the Callable and Future interfaces is the way to go.
Cancelling a FutureTask

Getting back to why interrupt() is the answer, the Future interface provides a method to cancel a pending or running task;

  boolean cancel(boolean mayInterruptIfRunning);

The method returns false if the task could not be cancelled, typically because it has already completed. As the argument suggests, you have a choice if the task has already started. You can let if run to completion but ignore the result (mayInterruptIfRunning = false) or you can try and interrupt it. Not surprisingly delving down into the code of the only concrete implementation of the Future interface provided by java.util.concurrent, FutureTask, you find the code

      if (mayInterruptIfRunning) {
          Thread r = runner;
          if (r != null)
             r.interrupt();
          }
      }

As you can see the default method of stopping a task in java.util.concurrent is to call interrupt(). This is why using interrupt() as described above, is now the preferable way stop threads. Java.util.concurrent takes care of clearing the thread's interrupted flag before it re-uses it.
Blocking I/O

As mentioned above threads are in the non-running state if they in a sleep() or wait() method or are blocking on I/O. Most read() methods block if data is not available and prior to Java V1.4 there was no means interrupting a blocking read(). However Java V1.4 introduced the InterruptibleChannel interface. Classes implementing the InterruptibleChannel interface can interrupt blocking I/O. This will cause the channel to be closed, the blocked thread to receive a ClosedByInterruptException, and the blocked thread's interrupt status to be set.

Lets look as a simple example of using an interruptible channel to overcoming a common blocking I/O problem. That of interrupting a thread that is blocked waiting for input from System.in. To do this we create an InputStreamReader that is based on an interruptible channel.

     new InputStreamReader(
           Channels.newInputStream(
                   (new FileInputStream(FileDescriptor.in)).getChannel())));

The following code is a simple example of its use. (Note: this program needs to be run from the command line. It does not work when run from within Eclipse)


import java.io.BufferedReader;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.channels.Channels;

public class InterruptInput {  
    static BufferedReader in = new BufferedReader(
            new InputStreamReader(
            Channels.newInputStream(
            (new FileInputStream(FileDescriptor.in)).getChannel())));
   
    public static void main(String args[]) {
        try {
            System.out.println("Enter lines of input (user ctrl+Z Enter to terminate):");
            System.out.println("(Input thread will be interrupted in 10 sec.)");
            // interrupt input in 10 sec
            (new TimeOut()).start();
            String line = null;
            while ((line = in.readLine()) != null) {
                System.out.println("Read line:'"+line+"'");
            }
        } catch (Exception ex) {
            System.out.println(ex.toString()); // printStackTrace();
        }
    }
   
    public static class TimeOut extends Thread {
        int sleepTime = 10000;
        Thread threadToInterrupt = null;   
        public TimeOut() {
            // interrupt thread that creates this TimeOut.
            threadToInterrupt = Thread.currentThread();
            setDaemon(true);
        }
       
        public void run() {
            try {
                sleep(10000); // wait 10 sec
            } catch(InterruptedException ex) {/*ignore*/}
            threadToInterrupt.interrupt();
        }
    }
}

Classes that implement InterruptibleChannel include, FileChannel, ServerSocketChannel, SocketChannel, Pipe.SinkChannel and Pipe.SourceChannel, so in principle you can interrupt I/O for files, sockets and pipes. The class TaskUtilities, which only requires Java 1.4 or higher, provides a number of static methods to create interruptible I/O for files.

Unfortunately there are still some problems with Sun's implementation of deriving Streams from Channels. These include

   1.

      flush does not flush
   2.

      you cannot interrupt a write to a file.

The problem with flush() is that when deriving an OutputStream from a channel, Sun has not linked the OutputStream's flush() method back to its underlying channel's force(true) method. This means if you interrupt a file being written to, the file will be closed but you may not get the previous data even if you have called flush(). This is mainly a problem for log files and for debugging.

The second problem is that when writing to a file, interrupt() will not interrupt a write in progress. This is technically correct as the I/O is not blocking but actually writing. However it means there is no way to interrupt writing a large block of data to a slow device. It would be useful if Sun modified the library code to allow writes to be interrupted. As it is you should avoid writing large blocks of data if you want the task to be responsive to interrupts. Instead write a number of small blocks in a loop. Then after you call interrupt(), at the next write() the InterruptibleChannel will be closed and a ClosedByInterruptException will be thrown. As an alternative, given the problems with flush() noted above, you may prefer not to use an interruptible file output, but instead write in small blocks and call TaksUtilities.ifInterruptedStop() between each write which will throw an InterruptedException if the thread has been interrupted.
Stopping Threads that have Not been Started

In this article I have suggested using the Thread's interrupted state as a flag to stop it, however in spite of what the Java docs on interrupt() say, a thread's interrupt state will not be set if it has not yet been started (by calling start()). Since each thread is an object is seems reasonable that its interrupted state be an internal field that can be set once the object has been created. Alas this is not the case in Java V1.5 and below. This means special handling is required to stop a thread between the time it is created and time it is started. If you are using java.util.concurrent Tasks this is handled for you automatically when you use cancel() on a FutureTask. The ThreadReturn package also handles this case via its ThreadReturn.stop(thread) method.

If you are writing your own small thread then you should follow the following example code.

    private volatile Thread myThread;
    public void stopMyThread() {
        Thread tmpThread = myThread;
        myThread = null;
        if (tmpThread != null) {
            tmpThread.interrupt();
        }
    }
    public void run() {
        if (myThread == null) {
           return; // stopped before started.
        }
        try {
            // all the run() method's code goes here
            ...
            // do some work
            Thread.yield(); // let another thread have some time perhaps to stop this one.
            if (Thread.currentThread().isInterrupted()) {
               throw new InterruptedException("Stopped by ifInterruptedStop()");
            }
            // do some more work
            ...
        } catch (Throwable t) {
           // log/handle all errors here
        }
    }

However most threads need to return a result or at least notify the caller if they fail, for these reasons you are better off using java.util.concurrent, FutureTalker or ThreadReturn even for small threads.
Summary

This article has covered some of the history of stopping Java threads and shown how both a stop flag and a call to interrupt() is required for prompt termination of threads. With the introduction of Java V1.5 java.util.concurrent expects interrupt() alone to stop the thread. To make this effective it was suggested the the thread's interrupt flag be used as the stop flag and an ifInterruptedStop() method be used to check for the interrupt flag being set and to throw an exception. Stopping the thread depends on two other things, i) that the interrupt exception thrown propagates back up and out of the run method (or call method) and ii) that the thread is in a running state. The problem of Blocking I/O was discussed and a solution based on InterruptibleChannels presented. The existing problems in the Sun library implementation where discussed.

你可能感兴趣的:(thread,Access,UP,sun,Exchange)