如何正确地中断java线程

  • Post author:
  • Post category:java

不提倡stop()方法

Thread.STOP()之类的api会造成一些不可预知的bug,所以很早便Deprecated了,真要纠结为什么请看这边文章为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?

当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,并抛出特殊的ThreadDeath()异常。这里的“立即”因为太“立即”了。比如,假如一个线程正在执行:

synchronized void {
     x = 3;
     y = 4;
}

 由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也干脆地stop了,这样就产生了不完整的残废数据。而多线程编程中最最基础的条件要保证数据的完整性,所以请忘记 线程的stop方法,以后我们再也不要说“停止线程”了。

interupt()中断线程

   一个线程从运行到真正的结束,应该有三个阶段:

  1. 正常运行.
  2. 处理结束前的工作,也就是准备结束.
  3. 结束退出.

   那么如何让一个线程结束呢?既然不能调用stop,可用的只有interrupt()方法。

    Thread类定义了如下关于中断的方法:

如果线程在运行中,interrupt()只是会设置线程的中断标志位,没有任何其它作用。那么如何让线程退出运行?对于一般逻辑,只要线程状态已经中断,我们就可以让它退出,因此可以使用isInterrupted()来判断线程的中断标志位,如下代码所示:

public class InterruptRunnableDemo extends Thread {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            // ... 单次循环代码
        }
        System.out.println("done ");
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new InterruptRunnableDemo();
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

这样InterruptRunnableDemo发现isInterrupted()为true,就会退出运行。

但是,在线程正在执行wait、sleep、join这些方法时,对线程对象调用interrupt()会使得该线程抛出InterruptedException,需要注意的是,抛出异常后,中断标志位会被清空(线程的中断标志位会由true重置为false,变成非中断状态,因为线程为了处理异常已经重新处于就绪状态。),而不是被设置为中断状态。如果catch语句没有处理异常,则下一次循环中isInterrupted()为false,线程会继续执行,可能你N次抛出异常,也无法让线程停止。比如说,执行如下代码:

Thread t = new Thread (){
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        //exception被捕获,但是为输出为false 因为标志位会被清空
            System.out.println(isInterrupted());
        }
    }        
};
t.start();
try {
    Thread.sleep(100);
} catch (InterruptedException e) {
}
t.interrupt();//置为true

这种情况下,大概有两种处理方式:

  1. 使用“二次惰性检测”(double check),把线程正确退出的方法称为“双重安全退出”,即不以isInterrupted ()为循环条件。而以一个标记作为循环条件。(这是JAVA推荐的方法)
  2. 捕获异常,进行合适的清理操作,清理后,一般应该调用Thread的interrupt方法设置中断标志位,或者直接return,使得其他代码有办法知道它发生了中断

第一种方式的示例代码如下:

public class ThreadA extends Thread {
   private boolean isInterrupted=false;
   int count=0;
   
   public void interrupt(){
       isInterrupted = true;
       super.interrupt();
   }
   
   public void run(){
       System.out.println(getName()+"将要运行...");
       while(!isInterrupted){
           System.out.println(getName()+"运行中"+count++);
           try{
               Thread.sleep(400);
           }catch(InterruptedException e){
               System.out.println(getName()+"从阻塞中退出...");
               System.out.println("this.isInterrupted()="+this.isInterrupted());

           }
       }
       System.out.println(getName()+"已经终止!");
   }
}

第二种方式的示例代码如下:

public class InterruptWaitingDemo extends Thread {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                // 模拟任务代码
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // ... 清理操作
                System.out.println(isInterrupted());//false
                // 重设中断标志位为true
                Thread.currentThread().interrupt();
            }
        }
        System.out.println(isInterrupted());//true
    }

    public static void main(String[] args) {
        InterruptWaitingDemo thread = new InterruptWaitingDemo();
        thread.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
        }
        thread.interrupt();
    }
}

或者直接return:

@Override
public void run(){
    for(int i = 0; i < 100; i++){
        System.out.println(this.getname()+":"+i);
        try{
            Thread.sleep(1000);
        }catch(InterruptedException e){
            System.out.println("异常结束!");
            return;  //在异常中直接返回,从而打断线程
        }
    }
}

NEW/TERMINATE

如果线程尚未启动(NEW),或者已经结束(TERMINATED),则调用interrupt()对它没有任何效果,中断标志位也不会被设置。比如说,以下代码的输出都是false。

public class InterruptNotAliveDemo {
    private static class A extends Thread {
        @Override
        public void run() {
        }
    }

    public static void test() throws InterruptedException {
        A a = new A();
        a.interrupt();
        System.out.println(a.isInterrupted());

        a.start();
        Thread.sleep(100);
        a.interrupt();
        System.out.println(a.isInterrupted());
    }

    public static void main(String[] args) throws InterruptedException {
        test();
    }
}

如何正确地取消/关闭线程

1. 以上,我们可以看出,interrupt方法不一定会真正”中断”线程,它只是一种协作机制,如果 不明白线程在做什么,不应该贸然的调用线程的interrupt方法,以为这样就能取消线程。

2. 对于以线程提供服务的程序模块而言,它应该封装取消/关闭操作,提供单独的取消/关闭方法给调用者,类似于InterruptReadDemo中演示的cancel方法,外部调用者应该调用这些方法而不是直接调用interrupt。

3. Java并发库的一些代码就提供了单独的取消/关闭方法,比如说,Future接口提供了如下方法以取消任务:boolean cancel(boolean mayInterruptIfRunning);

4. 再比如,ExecutorService提供了如下两个关闭方法:

void shutdown();
List<Runnable> shutdownNow();

5. Future和ExecutorService的API文档对这些方法都进行了详细说明,这是我们应该学习的方


版权声明:本文为huanghanqian原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。