4.如何终止线程

目录

1 如何终止正在执行的线程

2 如何终止被阻塞的线程

3.总结


线程终止是一个稍微复杂的问题,我们分运行状态和阻塞状态两种情况讨论。

1 如何终止正在执行的线程

首先我们思考一下,线程在什么情况下会终止?一般来说有如下几种情况:

第一种:当run方法完成后线程终止

run方法中的内容执行完后线程一般就自动结束了。

第二种:使用stop方法强行终止

该方法会强制关闭正在执行的线程,这种方法是不推荐的,因为假如很多指令正在执行,很多重要操作可能尚未完成,如果强制停止会导致潜在问题,例如一些清理性的工作没完成,如文件,数据库等的关闭。

也就说调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。看个例子:

public class ThreadStopExample extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 1000000; i++) {
                System.out.println("running code " + i);
            }
            System.out.println("the code finished");
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
​
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new ThreadStopExample();
        thread.start();
        Thread.sleep(10);
        thread.stop();
    }
}

该代码在执行的时候会抛出如下异常:

running code 327
running code 328
running code 329
java.lang.ThreadDeath
  at java.lang.Thread.stop(Thread.java:853)
  at part_b_inside.chapter2_thread_life.terminal_thread.ThreadStopExample.main(ThreadStopExample.java:20)

可以看到,该方法仅仅执行到i=329就结束了,后面的都还没有完成。如果这是一个线上的服务,例如正在处理账务等信息时就导致数据混乱,造成无法预知的问题,因此一定不能用stop()方法来中断线程。

第三种:通过发送信号来终止线程

其本质和开启类似,就是主线程给子线程发送一个可以关闭的信号,但是具体什么时候执行关闭由子线程决定。这就像你正在工作,女朋友突然打电话要你和她出去逛街,你说“稍等,我先将手上的工作完成”是一样的道理。也就是说main线程只给子线程发送信号来告知要结束,而不是暴力地直接将其停掉。具体是否要关闭由子线程根据自身状态决定是否停止。

那通过信号停止线程,具体工作是怎么样的呢?应用程序发送一个线程终止的信号给JVM,JVM处理之后转给操作系统,操作系统再转给CPU,CPU收到之后会自行决定是否终止,而不一定马上终止。CPU此时可能在执行某个原子操作,或者要完成finally的功能才终止操作等,也就是会等手头的工作完成再终止(也叫安全点 ,或者安全区域)。

在Java中,主要是通过interrupt和isInterruptted()。

在Thread中提供了一个interrupt()方法,从名字看表示中断,但实际上并不像stop()方法一样直接中断线程,而是向子线程发送一个中断的通知。例如,假如你是领导,对于在加班的同事,你会说”做完就下班吧,其他明天再说“。这就是你给他发的信号量,而不是强制让他走,同事可以根据自己的情况处理完再走,这个时间可能是一分钟,也可能是一小时,决定权在同事这里。这就是信号量的含义,也是线程安全中断的基本模型。

与interrupt()相配合的就是isInterruptted(),功能是判断是否收到了可以中断的请求。例如有的人一下午就看着领导走没走, 只要一走,立马开溜,这就是一直在通过isInterruptted()监听是否可以中断。

接下来,我们通过代码看一下上述过程:

public class InterruptDemo implements Runnable {
    @Override
    public void run() {
        while (true) {
          //执行操作  
        }
    }
}

很明显,上面的while无法结束。所以这里要将true改成能够判断当前线程的某个标记位,如果满足要求再继续循环,也就是这个代码:

public class InterruptDemo implements Runnable {
    @Override
    public void run() {
        //isInterrupted表示一个中断标记,默认是false
        while (!Thread.currentThread().isInterrupted()) {
          //执行操作
        }
    }
}

这样外部就可以通过设置该变量来终止了,像这个样子:

public class InterruptDemo implements Runnable {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
          //执行操作
        }
    }
​
    public static void main(String[] args) {
        Thread thread=new Thread(new InterruptDemo());
        thread.start();
        //这里将共享变量isInterrupted设置为true,从而终止子线程
        thread.interrupt();
    }
}

具体是怎么实现的呢?很遗憾,两个都是native方法,不能直接看:

private native void interrupt0();
private native boolean isInterrupted(boolean ClearInterrupted);

结论

对于正在执行的线程,如何优雅地将其终止的: main线程无法确定子线程的当前状态,可以通过interrupt()指令来给其他线性发送终止信号,而接收方通过isInterrupted()来监听是否可以终止线程,收到信号之后可以自行决定是否终止。

那native方法具体是咋回事呢,我们后面讲解Unsafe时再看。

2 如何终止被阻塞的线程

如果线程是sleep,join和waiting等状态,此时没有获得CPU时间片,也就无法及时感知到isInterrupted状态的变化,此时该如何中断呢?例如,假如某个人正在睡觉,如果发了通知,他根本不知道,例如这个代码:

public class InterruptDemo2 implements Runnable {
    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(20000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
      public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new InterruptDemo2());
        t1.start();
        Thread.sleep(1000);
        t1.interrupt(); 
    }
}

我们可以发现上面的代码是可以停止的,但是为什么所有睡眠的代码,都要求必须处理InterruptedException异常呢?就是为了利用异常机制来唤醒阻塞的线程的。

这说明虽然线程是阻塞模式,但是触发异常之后就能获得CPU时间片来响应中断,并终止。 假如代码块在一个while自旋中,如何停止呢? 看例子:

public class InterruptDemo02  implements Runnable{
    @Override
    public void run() {
        while(!Thread.currentThread().isInterrupted()){ //①默认false,不做处理时中断之后会线程还挂着,不会退出
            try {
                TimeUnit.SECONDS.sleep(200);
                System.out.println("run .....");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("processor End");
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new InterruptDemo02());
        t1.start();
        Thread.sleep(100);
        t1.interrupt();  
    }
}

这个例子可以看到,抛出了sleep interrupt的异常,但是子线程并没有终止。

运行到100ms时,主线程触发了子线程的interrupt exception,这个异常会触发线程复位。

看下面的例子,特别是注释的内容:本来在子线程中Thread.currentThread().isInterrupted()是false的,当主线程执行t1.interrupt();时将其改成了true①。 子线程中的异常再次将Thread.currentThread().isInterrupted()改成了false②,所以子线程会继续循环,而不会停止。 如果要终止,③需要catch里再设置一次中断,也就是这样:

public void run() {
    while (!Thread.currentThread().isInterrupted()) { //①默认false,不做处理时中断之后会线程还挂着,不会退出
        try {
            TimeUnit.SECONDS.sleep(200);
            System.out.println("run .....");
        } catch (InterruptedException e) {//②,这里会将isInterrupted再改成true
            e.printStackTrace();
            System.out.println("interrupt...");
            Thread.currentThread().interrupt();//③子线程自己设置一次中断,此时子线程才会真正中断
        }
    }
    System.out.println("processor End");
}

执行结果是:

可以看到打印了,线程也停止了。

通过上面的代码我们可以看到,对于涉及线程阻塞的方法,例如Thread.join(),Thread.wait(),Thread.sleep()等等,都会抛出异常。之所以如此,是因为如果需要让一个处于阻塞的线程被中断,从而做出响应。

我们再强调一下,主线程想让子线程停止要通过interrupt给子线程发一个信号,告诉要中断了,而不是强制将其中断。触发复位是为了让子线程保持原来的状态,是否中断则有子线程在catch中决定。如果不处理就不中断,如果要中断就是再执行一次Thread.currentThread().interrupt();

上面这个逻辑可以这么想象:放假的早上你正在睡懒觉,你妈叫你起来吃饭(给子线程发一个中断睡眠的状态),你被临时叫醒了,告诉她你知道啦(子线程的中断被临时打破,并且给主线程一个响应,异常就是为了干这个的,而不是出错了),如果不给她回话,她可能会一直叫你,甚至砸你的门。但是决定权在你这里,如果你不搭理还会继续睡(睡眠复位),你还可以决定那我起床吧(再次给自己一个不睡眠信号),然后起床(真正执行中断睡眠,开始起床的操作)。 如果看图就这样子:

  main线程通过interrupt()通过修改子线程的共享变量isInterrupted状态来告诉子线程要终止,但是自己并不知道子线程当前的状态,也不能让子线程强制终止。子线程根据这个变量来判断自己是否应该终止。

3.总结

本节我们介绍了线程的几个基础问题,例如如何创建线程,生命周期是怎么样的。其中几个重点我们务必理解清楚:

1.开启线程为什么要用start(),用户线程、JVM、操作系统和CPU分别做了什么。

2.线程的生命周期有几种状态,每种状态的特征是什么,sleep、waiting和time_waiting有什么区别和联系。

3.如何优雅的终止线程?此时用户线程、JVM、操作系统和CPU又分别做了什么。

4.如何终止正在阻塞的线程?此时子线程和主线程是如何交互的。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值