3.线程的八大核心基础知识之如何正确停止线程

Posted zhihaospace

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了3.线程的八大核心基础知识之如何正确停止线程相关的知识,希望对你有一定的参考价值。

一.概述

技术图片

 

 

二.原理介绍

  • 使用interrupt来通知,而不是强制

解释:由于我们无法强行停止线程,只是告诉线程需要停止了,线程才可能进行收尾清理最后停止,也可能停止不了。控制权不在我们手中。

三.最佳实践:如何正确停止线程

 

1.正确的停止方法:interrupt

(1)普通情况下如何停止线程

代码一:使用interrupt通知子线程,但子线程并不停止

/**
 * run方法内没有sleep或wait方法时,停止线程
 */
public class RightWayStopThreadWithoutSleep implements Runnable{

    @Override
    public void run() {
        int num = 0;
        while(num <= Integer.MAX_VALUE / 2){
            if (num % 10000 == 0){
                System.out.println(num+"是10000的倍数");
            }
            num++;
        }
        System.out.println("任务运行结束");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
        //在主线程中开启子线程,然后主线程休眠1s后通知子线程中断
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

技术图片

 

 

 方法二:在子线程中接收中断通知,并处理中断

  • 修改while语句 while(!Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE / 2) 

技术图片

(2)在线程阻塞情况下如何停止线程(非阻塞循环

  • 子线程使用sleep睡眠1s时,主线程在睡眠0.5s后通知正在睡眠的子线程中断,会发生异常

  • 加上 && !Thread.currentThread().isInterrupted() 是为了防止在非阻塞的while循环中出现中断

  • 在线程阻塞的情况下,主线程使用interrupt通知子线程中断然后在子线程中判断是否被中断最后使用try...catch捕获处理即可(try...catch在while外面)

/**
 * run方法内带有sleep或wait方法时,停止线程
 */
public class RightWayStopThreadWithSleep{

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;
            try {
                while (num <= 300 && !Thread.currentThread().isInterrupted()){
                    if (num % 100 == 0){
                        System.out.println(num + "是100的倍数");
                    }
                    num++;
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
    }
}

技术图片

(3)如果线程在每次迭代后都阻塞(阻塞循环

  • 每次循环中都有一段时间阻塞,所以在阻塞循环中我们不需要 && !Thread.currentThread().isInterrupted() ,因为每次都是在Thread.sleep(10)中出现中断

  • 在线程迭代阻塞的情况下,主线程使用interrupt通知子线程中断然后在子线程使用try...catch捕获处理即可(try...catch在while外面)

/**
 * run方法内每次循环都会带有sleep或wait方法时,停止线程
 */
public class RightWayStopThreadWithSleepEveryLoop {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;
            try {
                while (num <= 10000){
                    if (num % 100 == 0){
                        System.out.println(num + "是100的倍数");
                    }
                    num++;
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

(4)while内try/catch的问题

  • 由于sleep函数响应中断之后会把interrupt标记位清除,所以一直在while循环中执行

public class CantInterrupt {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;

            while (num <= 10000) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍数");
                }
                num++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

技术图片

 

 

 修改while循环  while (num <= 10000 && !Thread.currentThread().isInterrupted()) ,结果一样没有区别

(5)实际开发中的两种最佳实践

  • 优先选择:传递中断

  • 不想或无法传递:恢复中断

  • 不应该屏蔽中断,要使用上面两种最佳实践

【1】优先传递中断

  • 在run方法中调用的函数,需要将异常传递给run方法处理而不是自己处理

  • 自己处理就会出现中断被清除,run方法中的循环停止不了

/**
 * 最佳实践:catch了InterruptedException之后优先选择:在方法签名中抛出异常
 * 最佳做法:必须在run中try/catch来处理异常而不是在run的内部调用方法try/catch,
 * 并且run方法无法抛出异常
 */
public class RightWayStopThreadInProd implements Runnable {
    @Override
    public void run() {

        while (true && !Thread.currentThread().isInterrupted()) {
            System.out.println("go");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                System.out.println("保存日志");
                e.printStackTrace();
            }
        }
    }

    private void throwInMethod() throws InterruptedException {
        Thread.sleep(2000);
    }

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

        Thread thread = new Thread(new RightWayStopThreadInProd());

        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }

}

【2】不想或无法传递中断时就恢复中断

  • 在方法中添加Thread.currentThread().interrupt()用来传递给run方法中断

/**
 * 最佳实践:被调用的方法中在catch语句中调用Thread.currentThread().interrupt来恢复中断
 */
public class RightWayStopThreadInProd2 implements Runnable {
    @Override
    public void run() {

        while (true) {
            if (!Thread.currentThread().isInterrupted()){
                System.out.println("Interrupted,程序运行结束");
                break;
            }
            throwInMethod();
        }
    }

    private void throwInMethod() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }

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

        Thread thread = new Thread(new RightWayStopThreadInProd2());

        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

(6)响应中断的方法总结列表

技术图片

 

  技术图片

2.正确停止带来的好处

  • 目的是线程安全并且数据完整性得到了保障

3.补充内容:Java异常体系

  • Error:出现严重错误,捕获了也没用已经处理不了了

  • RuntimeException:unchecked exception(不受检查异常),程序不可预知是由于程序员在写程序的时候出现的错误,需要修改程序防止RuntimeException

  • 其他的Exception:checked exception(受检查异常),程序可预知的异常,必须处理

技术图片

四.错误的停止线程的方法

1.被弃用的stop,suspendresume方法

  • stop会立即停止,对于像银行转账业务就会造成巨大影响

  • suspend和resume会带着锁休息这可能会造成死锁

2.用volatile设置boolean标记位

(1)看上去可行

 

(2)错误原因

(3)修正方案

五.重要函数的源码解析

六.彩蛋

七.常见面试问题

 

以上是关于3.线程的八大核心基础知识之如何正确停止线程的主要内容,如果未能解决你的问题,请参考以下文章

笔记-线程八大核心总纲

线程八大基础核心二(启动线程)

线程八大基础核心六(线程属性)

线程八大基础核心四(线程生命周期)

线程八大基础核心一(创建线程的方式)

线程八大基础核心五(线程相关方法一)