Thread之十:中止线程方法汇总

在上篇文章《多线程的使用——Thread类和Runnable接口》中提到中断线程的问题。在JAVA中,曾经使用stop方法来中止线程,然而,该方法具备固有的不安全性,于是已经被抛弃(Deprecated)。那么应该怎么结束一个进程呢?官方文档中对此有详细说明:《为什么不同意使用 Thread.stop、Thread.suspend 和 Thread.resume?》。在此引用stop方法的说明:html

1. Why is Thread.stop deprecated?
Because it is inherently unsafe. Stopping a thread causes it to unlock all the monitors that it has locked. (The monitors are unlocked as the ThreadDeath exception propagates up the stack.) If any of the objects previously protected by these monitors were in an inconsistent state, other threads may now view these objects in an inconsistent state. Such objects are said to be damaged. When threads operate on damaged objects, arbitrary behavior can result. This behavior may be subtle and difficult to detect, or it may be pronounced. Unlike other unchecked exceptions, ThreadDeath kills threads silently; thus, the user has no warning that his program may be corrupted. The corruption can manifest itself at any time after the actual damage occurs, even hours or days in the future.
大概意思是:
由于该方法本质上是不安全的。中止一个线程将释放它已经锁定的全部监视器(做为沿堆栈向上传播的未检查 ThreadDeath 异常的一个天然后果)。若是之前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其余线程可见,这有可能致使任意的行为。此行为多是微妙的,难以察觉,也多是显著的。不像其余的未检查异常,ThreadDeath异常会在后台杀死线程,所以,用户并不会获得警告,提示他的程序可能已损坏。这种损坏有可能在实际破坏发生以后的任什么时候间表现出来,也有可能在多小时甚至在将来的不少天后。
在文档中还提到,程序员不能经过捕获ThreadDeath异常来修复已破坏的对象。具体缘由见原文。
既然stop方法不建议使用,那么应该用什么方法来代理stop已实现相应的功能呢?
 
一、经过修改共享变量来通知目标线程中止运行
 
大部分须要使用stop的地方应该使用这种方法来达到中断线程的目的。
这种方法有几个要求或注意事项:
(1)目标线程必须有规律的检查变量,当该变量指示它应该中止运行时,该线程应该按必定的顺序从它执行的方法中返回。
(2)该变量必须定义为 volatile或者全部对它的访问必须同步(synchronized)。
例如:
package chapter2; 

public class ThreadFlag extends Thread 
{ 
    public volatile boolean exit = false; 

    public void run() 
    { 
        while (!exit); 
    } 
    public static void main(String[] args) throws Exception 
    { 
        ThreadFlag thread = new ThreadFlag(); 
        thread.start(); 
        sleep(5000); // 主线程延迟5秒 
        thread.exit = true;  // 终止线程thread 
        thread.join(); 
        System.out.println("线程退出!"); 
    } 
} 

 在上面代码中定义了一个退出标志exit,当exit为true时,while循环退出,exit的默认值为false.在定义exit时,使用了一个Java关键字volatile,这个关键字的目的是使exit同步,也就是说在同一时刻只能由一个线程来修改exit的值。java

二、经过Thread.interrupt方法中断线程
一般状况下,咱们应该使用第一种方式来代替Thread.stop方法。然而如下几种方式应该使用Thread.interrupt方法来中断线程(该方法一般也会结合第一种方法使用)。
一开始使用interrupt方法时,会有莫名奇妙的感受:难道该方法有问题?
API文档上说,该方法用于"Interrupts this thread"。请看下面的例子:
package com.jvm.study.thread;

public class TestThread implements Runnable{ 
     
    boolean stop = false; 
    public static void main(String[] args) throws Exception { 
        Thread thread = new Thread(new TestThread(),"My Thread"); 
        System.out.println( "Starting thread..." ); 
        thread.start(); 
        Thread.sleep( 3000 ); 
        System.out.println( "Interrupting thread..." ); 
        thread.interrupt(); 
        System.out.println("线程是否中断:" + thread.isInterrupted()); 
        Thread.sleep( 3000 ); 
        System.out.println("Stopping application..." ); 
    } 
    public void run() { 
        while(!stop){ 
            System.out.println( "My Thread is running..." ); 
            // 让该循环持续一段时间,使上面的话打印次数少点 
            long time = System.currentTimeMillis(); 
            while((System.currentTimeMillis()-time < 1000)) { 
            } 
        } 
        System.out.println("My Thread exiting under request..." ); 
    } 
} 
运行后的结果是:
Starting thread...
My Thread is running...
My Thread is running...
My Thread is running...
My Thread is running...
Interrupting thread...
线程是否中断:true
My Thread is running...
My Thread is running...
My Thread is running...
Stopping application...
My Thread is running...
My Thread is running...
……
应用程序并不会退出,启动的线程没有由于调用interrupt而终止,但是从调用isInterrupted方法返回的结果能够清楚地知道该线程已经中断了。那位什么会出现这种状况呢?究竟是interrupt方法出问题了仍是isInterrupted方法出问题了?在Thread类中还有一个测试中断状态的方法(静态的)interrupted,换用这个方法测试,获得的结果是同样的。由此彷佛应该是interrupt方法出问题了。实际上,在JAVA API文档中对该方法进行了详细的说明。该方法实际上只是设置了一个中断状态, 当该线程因为下列缘由而受阻时,这个中断状态就能起做用了
(1)若是线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程当中受阻,则其中断状态将被清除,它还将收到一个InterruptedException异常。这个时候,咱们能够经过捕获InterruptedException异常来终止线程的执行,具体能够经过return等退出或改变共享变量的值使其退出。
(2)若是该线程在可中断的通道上的 I/O 操做中受阻,则该通道将被关闭,该线程的中断状态将被设置而且该线程将收到一个 ClosedByInterruptException。这时候处理方法同样,只是捕获的异常不同而已。
其实对于这些状况有一个通用的处理方法:
package com.jvm.study.thread;

public class TestThread2 implements Runnable {

    boolean stop = false;

    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(new TestThread2(), "My Thread2");
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(10000);
        System.out.println("Interrupting thread...");
        thread.interrupt();
        System.out.println("线程是否中断:" + thread.isInterrupted());
        Thread.sleep(3000);
        System.out.println("Stopping application...");
        Thread.sleep(30000);
    }

    public void run() {
        while (!stop) {
            System.out.println("My Thread is running...");
            // 让该循环持续一段时间,使上面的话打印次数少点
            long time = System.currentTimeMillis();
            while ((System.currentTimeMillis() - time < 1000)) {
            }
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
        }
        System.out.println("My Thread exiting under request...");
    }
}
由于调用interrupt方法后,会设置线程的中断状态,因此,经过监视该状态来达到终止线程的目的。这些在《 Thread之八:interrupt中断》也提到过。
 
总结:程序应该对线程中断做出恰当的响应。响应方式一般有三种:(来自温绍锦(昵称:温少):http//www.cnblogs.com/jobs/)

注意:interrupted与isInterrupted方法的区别(见API文档)程序员

 

 当线程处于下面的Blocked状态,经过调用interrupt()方法来中止线程:编程

2.一、当线程处于下面的情况时(1)若是线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程当中受阻,则其中断状态将被清除,它还将收到一个InterruptedException异常。这个时候,咱们能够经过捕获InterruptedException异常来终止线程的执行,具体能够经过return等退出或改变共享变量的值使其退出。),属于非运行状态:api

  • 当sleep方法被调用。缓存

  • 当wait方法被调用。安全

  • 当被I/O阻塞,多是文件或者网络等等。网络

当线程处于上述的状态时,使用前面介绍的方法就不可用了。这个时候,咱们可使用interrupt()来打破阻塞的状况,如:多线程

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

 

interrupt()被调用的时候,InterruptedException将被抛出,因此你能够再run方法中捕获这个异常,让线程安全退出:oracle

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

 

2.二、阻塞的I/O((2)若是该线程在可中断的通道上的 I/O 操做中受阻,则该通道将被关闭,该线程的中断状态将被设置而且该线程将收到一个 ClosedByInterruptException。这时候处理方法同样,只是捕获的异常不同而已

当线程被I/O阻塞的时候,调用interrupt()的状况是依赖与实际运行的平台的。在Solaris和Linux平台上将会抛出InterruptedIOException的异常,可是Windows上面不会有这种异常。因此,咱们处理这种问题不能依靠于平台的实现。如:

package com.cnblogs.gpcuster

import java.net.*;
import java.io.*;

public abstract class InterruptibleReader extends Thread {

    private Object lock = new Object( );
    private InputStream is;
    private boolean done;
    private int buflen;
    protected void processData(byte[] b, int n) { }

    class ReaderClass extends Thread {

        public void run( ) {
            byte[] b = new byte[buflen];

            while (!done) {
                try {
                    int n = is.read(b, 0, buflen);
                    processData(b, n);
                } catch (IOException ioe) {
                    done = true;
                }
            }

            synchronized(lock) {
                lock.notify( );
            }
        }
    }

    public InterruptibleReader(InputStream is) {
        this(is, 512);
    }

    public InterruptibleReader(InputStream is, int len) {
        this.is = is;
        buflen = len;
    }

    public void run( ) {
        ReaderClass rc = new ReaderClass( );

        synchronized(lock) {
            rc.start( );
            while (!done) {
                try {
                    lock.wait( );
                } catch (InterruptedException ie) {
                    done = true;
                    rc.interrupt( );
                    try {
                        is.close( );
                    } catch (IOException ioe) {}
                }
            }
        }
    }
} 

另外,咱们也可使用InterruptibleChannel接口。 实现了InterruptibleChannel接口的类能够在阻塞的时候抛出ClosedByInterruptException。如:

package com.cnblogs.gpcuster

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();
        }
    }
}

 

这里还须要注意一点,当线程处于写文件的状态时,调用interrupt()不会中断线程。

 

3、不提倡的stop()方法 
臭名昭著的stop()中止线程的方法已不提倡使用了,缘由是什么呢?
 当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会当即中止,并抛出特殊的ThreadDeath()异常。这里的“当即”由于太“当即”了,

    在java多线程编程中,线程的终止能够说是一个必然会遇到的操做。可是这样一个常见的操做其实并非一个可以垂手可得实现的操做,并且在某些场景下状况会变得更复杂更棘手。

        Java标准API中的Thread类提供了stop方法能够终止线程,可是很遗憾,这种方法不建议使用,缘由是这种方式终止线程中断临界区代码执行,并会释放线程以前获取的监控器锁,这样势必引发某些对象状态的不一致(由于临界区代码通常是原子的,不会被干扰的),具体缘由能够参考资料[1]。这样一来,就必须根据线程的特色使用不一样的替代方案以终止线程。根据中止线程时线程执行状态的不一样有以下中止线程的方法。

Thread之九:stop

4、 处于运行状态的线程中止

        处于运行状态的线程就是常见的处于一个循环中不断执行业务流程的线程,这样的线程须要经过设置中止变量的方式,在每次循环开始处判断变量是否改变为中止,以达到中止线程的目的,好比以下代码框架:

private volatile Thread blinker;  
public void stop() {  
        blinker = null;  
}  
public void run() {  
        Thread thisThread = Thread.currentThread();  
        while (blinker == thisThread) {  
            try {  
                //业务流程  
            } catch (Exception e){}  
        }  
}

 

        若是主线程调用该线程对象的stop方法,blinker对象被设置为null,则线程的下次循环中blinker!=thisThread,于是能够退出循环,并退出run方法而使线程结束。将引用变量blinker的类型前加上volatile关键字的目的是防止编译器对该变量存取时的优化,这种优化主要是缓存对变量的修改,这将使其余线程不会马上看到修改后的blinker值,从而影响退出。此外,Java标准保证被volatile修饰的变量的读写都是原子的。

        上述的Thread类型的blinker彻底能够由更为简单的boolean类型变量代替。

5、 即将或正在处于非运行态的线程中止

        线程的非运行状态常见的有以下两种状况:

可中断等待:线程调用了sleep或wait方法,这些方法可抛出InterruptedException;

Io阻塞:线程调用了IO的read操做或者socket的accept操做,处于阻塞状态。

上面已经讲过。

3 处于大数据IO读写中的线程中止

         处于大数据IO读写中的线程实际上处于运行状态,而不是等待或阻塞状态,所以上面的interrupt机制不适用。线程处于IO读写中能够当作是线程运行中的一种特例。中止这样的线程的办法是强行close掉io输入输出流对象,使其抛出异常,进而使线程中止。

        最好的建议是将大数据的IO读写操做放在循环中进行,这样能够在每次循环中都有线程中止的时机,这也就将问题转化为如何中止正在运行中的线程的问题了。

4 在线程运行前中止线程

         有时,线程中的run方法须要足够健壮以支持在线程实际运行前终止线程的状况。即在Thread建立后,到Thread的start方法调用前这段时间,调用自定义的stop方法也要奏效。从上述的中止处于等待状态线程的代码示例中,stop方法并不能终止运行前的线程,由于在Thread的start方法被调用前,调用interrupt方法并不会将Thread对象的中断状态置位,这样当run方法执行时,currentThread的isInterrupted方法返回false,线程将继续执行下去。

         为了解决这个问题,不得不本身再额外建立一个volatile标志量,并将其加入run方法的最开头:

public void run() {  
        if (myThread == null) {  
           return; // stopped before started.  
        }  
        while(!Thread.currentThread().isInterrupted()){  
    //业务逻辑  
        }  
}  

 

         还有一种解决方法,也能够在run中直接使用该自定义标志量,而不使用isInterrupted方法判断线程是否应该中止。这种方法的run代码框架实际上和中止运行时线程的同样。

相关文章
相关标签/搜索