Java多线程:如何停止/中断一个运行中的线程?

简介: Java多线程:如何停止/中断一个运行中的线程?

# 面试题:


  • 如何正确地停止/中断一个线程
  • 哪些情况下线程会停止
  • 如何处理不可中断的阻塞


# 核心思想


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


# 代码演示


  • 场景1:run()方法中没有sleep()/wait()等会响应中断的方法。
    1.1  线程未处理中断:

/**
 * 正确停止线程---run()方法内没有sleep()或者wait()方法-未处理中断信号
 *
 * @author futao
 * @date 2020/6/6
 */
public class StopThreadWithoutSleepWait implements Runnable {
    @Override
    public void run() {
        unHandleInterrupt();
    }
    /**
     * 未处理中断
     */
    public void unHandleInterrupt() {
        int num = 0;
        //打印最大整数一半的范围内10000的倍数
        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 StopThreadWithoutSleepWait());
        //启动线程
        thread.start();
        //增加子线程处于运行状态的可能性
        Thread.sleep(500L);
        //尝试中断子线程
        thread.interrupt();
    }
}


  • 期望:子线程在执行500毫秒之后停下来。
  • 结果:线程并没有停下来。原因是:我们并未处理线程的中断信号。


image.png


image.png

  • 1.2 对程序进行改进:响应中断。


  • 在while循环条件中判断当前线程是否被中断(Thread.currentThread().isInterrupted()),如果未被中断才继续执行,被中断则跳出while循环。

package com.futao.learn.threads.c_如何停止线程;
/**
 * 正确停止线程---run()方法内没有sleep()或者wait()方法
 *
 * @author futao
 * @date 2020/6/6
 */
public class StopThreadWithoutSleepWait implements Runnable {
    @Override
    public void run() {
        handleInterrupt();
    }
    /**
     * 响应中断
     */
    public void handleInterrupt() {
        int num = 0;
        //加入线程未被中断的条件
        while (!Thread.currentThread().isInterrupted() && 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 StopThreadWithoutSleepWait());
        //启动线程
        thread.start();
        //增加子线程处于运行状态的可能性
        Thread.sleep(500L);
        //尝试中断子线程
        thread.interrupt();
    }
}


  • 期望:线程在500毫秒之后响应中断,停下来。
  • 结果:线程成功响应中断,提前结束。


image.png

image.png

  • 总结可得出:线程调用者可以向线程发出中断请求,但是线程中断的权利控制在线程代码的编写者是否响应了你的中断请求。线程代码的编写者比调用者更加了解线程应不应该被停止,何时停止。


  • 场景2:run()方法中存在sleep()/wait()等会响应中断的方法。(响应中断的方法会抛出InterruptedException)
    2.1 sleep()在while循环外

package com.futao.learn.threads.c_如何停止线程;
/**
 * 中断线程-run()方法中有sleep()或者wait()方法
 *
 * @author futao
 * @date 2020/6/6
 */
public class StopThreadWithSleep {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            int num = 0;
            while (!Thread.currentThread().isInterrupted() && num <= 300) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的整数倍");
                }
                ++num;
            }
            try {
                //sleep()方法会响应中断,且响应中断的方式为抛出InterruptException异常--- sleep interrupted
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程执行完毕");
        });
        //启动线程
        thread.start();
        //等待while循环执行完毕
        Thread.sleep(200L);
        //当线程处于sleep()状态时进行中断
        thread.interrupt();
    }
}


  • 预期:程序执行完while循环之后,阻塞在sleep()方法,此时进行中断,sleep()方法响应该中断,抛出InterruptedException,打印异常堆栈。
  • 测试:符合预期。


image.png


  • 2.2 无法停止的线程:sleep()方法在while循环内。
  • 你预期下面代码的执行结果是怎样的?

/**
 * 3. 无法停止的线程
 *
 * @author futao
 * @date 2020/6/6
 */
public class CantStopThread {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            int num = 1;
            while (num <= 1000 && !Thread.currentThread().isInterrupted()) {
                if (num % 2 == 0) {
                    System.out.println(num + "是2的整数倍");
                }
                ++num;
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程执行完毕");
        });
        //启动线程
        thread.start();
        //主线程休眠500毫秒
        Thread.sleep(500L);
        //中断线程
        thread.interrupt();
    }
}


  • 预期:线程在第一次进入while循环时,进入休眠1000毫秒状态,在500毫秒时主线程向子线程发出中断信号,sleep()方法响应中断,打印异常堆栈,下次再进入while循环时,因为线程被设置成了中断状态,所以while中条件不成立,不应该继续执行。但是实际上是这样吗?
  • 结果:slee()响应了中断,打印了异常堆栈。但是线程并没有停下来,而是继续执行。就像什么都没有发生一样。


image.png


  • 原因:sleep()在响应了中断之后,清除了线程的中断状态。那么while判断时不知道线程被中断了。


  • 查看sleep()方法的描述:当InterruptedException异常被抛出后,线程的中断状态将被清除。


image.png

类似的,查看Object.wait()的方法描述。

image.png

  • 类似的会响应中断的方法还有那些?
  • 响应中断的方法总结
  • Object.wait()/wait(long)/wait(long,int)
  • Thread.sleep(long)/sleep(long,int)
  • Thread.join()/join(long)/join(long,int)
  • juc.BlockingQueue.take()/put(E)
  • juc.Lock.lockInterruptibly()
  • juc.CountDownLatch.await()
  • juc.CyclicBarrier.await()
  • juc.Exchanger.exchange(V)
  • jio.InterruptibleChannel相关方法
  • jio.Selector相关方法
  • 那么,线程响应中断后应该怎么处理。


# 线程中断的最佳实践:


  • 传递中断
  • 不想或无法传递:恢复中断
  • 核心思想:不应屏蔽中断
  1. 传递中断:在方法签名中将中断异常抛出,而不是生吞,交给调用者处理。

/**
 * 正确停止线程的方式1-抛出中断
 * 优先在方法签名中抛出该异常
 *
 * @author futao
 * @date 2020/6/6
 */
public class RightWayToStopThread implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("running...");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("响应中断,跳出循环,停止线程");
                break;
            }
        }
    }
    /**
     * 业务方法应该将中断异常抛出,将异常传递给上层--传递中断
     *
     * @throws InterruptedException
     */
    private void throwInMethod() throws InterruptedException {
        System.out.println("业务执行中.....");
        Thread.sleep(2000L);
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayToStopThread());
        thread.start();
        Thread.sleep(1000L);
        thread.interrupt();
    }
}


  • 结果:


image.png


  1. 不想或无法传递时:应该恢复中断(Thread.currentThread().interrupt())

/**
 * 正确停止线程的方式2
 * 恢复中断
 *
 * @author futao
 * @date 2020/6/6
 */
public class RightWayToStopThreadReInterrupt implements Runnable {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("running...");
            throwInMethod();
        }
        System.out.println("线程任务执行完毕");
    }
    private void throwInMethod() {
        try {
            Thread.sleep(2000L);
        } catch (InterruptedException e) {
            System.out.println("感知到中断请求。");
            System.out.println("重新设置中断信号");
            //尝试恢复中断
            Thread.currentThread().interrupt();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayToStopThreadReInterrupt());
        thread.start();
        Thread.sleep(1000L);
        thread.interrupt();
    }
}


  • 结果:


image.png

# 线程中断的相关方法


  • 预期下面代码的执行结果?

/**
 * 线程中断的相关方法
 *
 * @author futao
 * @date 2020/6/7
 */
public class InterruptMethod {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("线程任务执行中...");
            while (true) {
            }
        });
        //启动线程
        thread.start();
        System.out.println(thread.isInterrupted());
        //向线程发送中断信号
        thread.interrupt();
        System.out.println(thread.isInterrupted());
        System.out.println(thread.isInterrupted());
        System.out.println(Thread.interrupted());
        System.out.println(thread.isInterrupted());
        System.out.println(thread.interrupted());
        System.out.println(thread.isInterrupted());
    }
}
  • 结果:

false
true
true
false
true
false
true


  • 分析:
  1. 线程处于运行状态,且没有程序给线程发送中断信号。所以非中断状态
  2. 调用了中断方法,所以线程状态状态为true
  3. 由于thread.isInterrupted()并不会清除线程的中断状态,所以多次调用,返回的结果一样,依旧为已中断
  4. Thread.interrupted()判断的是执行这行代码的线程的中断状态。这里是主线程,所以为未中断。且该方法调用之后,会将执行该方法的线程的中断状态清除。
  5. 因为Thread.interrupted()清除的是执行代码的线程的中断状态,所以不印象子线程的中断状态,所以子线程的中断状态仍然为true。
  6. 如果子线程对象直接调用静态方法interrupted(),返回的也是执行这段代码的线程的中断状态。此时为主线程,状态为未中断。
  7. 子线程对象直接调用静态方法interrupted()并不会清除调用对象的线程中断状态,而是清除执行这段代码的线程的中断状态。所以子线程的中断状态不影响。
  • 为什么通过子线程对象来执行静态方法static boolean interrupted()清除的是执行者的中断状态呢?查看源码发现,静态方法static boolean interrupted()会先获取到当前执行这段代码的线程,清除其中断状态,并返回中断状态。


image.png


  • 总结:
  1. thread.interrupt()  给线程发送中断信号,设置线程thread的中断状态为true。
  2. thread.isInterrupted()  判断线程thread是否被中断。且不改变线程的中断状态
  3. Thread.interrupted()/thread.interrupted() 判断执行这行代码的线程的中断状态,并且清除其中断状态。
  4. private native boolean isInterrupted(boolean ClearInterrupted); native方法,真正判断线程中断状态和清除中断状态的代码。thread.isInterrupted()Thread.interrupted()/thread.interrupted()最终调用的都是这个方法。
  • Q:如何清除线程的中断状态? 执行Thread.interrupted();这行代码的线程的中断状态会被清除。


# 哪些情况下线程会停止


  1. 线程run()方法正常执行完毕。(可借助线程中断机制提前结束run()方法)
  2. 线程发生了未捕获的异常。


# 错误的停止线程的方式


  • 被弃用的stop()suspend()resume()
  • 使用volatile设置boolean标记位的方式,不可靠


# 如何处理不可中断的阻塞


  • 并不是所有的阻塞都会响应中断,例如IO中的InputStream.read()。处理这类问题的方式要视情况而定,大概思路是手动编写程序检测线程的中断状态,如果线程被中断,则手动调用例如InputStream.close()方法来关闭流,实现停止线程。

# 本文源代码


https://github.com/FutaoSmile/learn-thread/tree/master/src/main/java/com/futao/learn/threads/c_%E5%A6%82%E4%BD%95%E5%81%9C%E6%AD%A2%E7%BA%BF%E7%A8%8B


# 系列文章


Java多线程:线程的创建与启动

相关文章
|
7月前
|
Java 测试技术 API
【JUC】(1)带你重新认识进程与线程!!让你深层次了解线程运行的睡眠与打断!!
JUC是什么?你可以说它就是研究Java方面的并发过程。本篇是JUC专栏的第一章!带你了解并行与并发、线程与程序、线程的启动与休眠、打断和等待!全是干货!快快快!
1102 2
|
7月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
396 1
|
7月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
368 1
|
8月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
354 0
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
692 1
|
安全 算法 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(下)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
272 6
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(中)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
307 5
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(上)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
231 3
|
设计模式 并行计算 安全
Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
288 0

热门文章

最新文章