前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >如何停止/中断一个运行中的线程

如何停止/中断一个运行中的线程

作者头像
用户1516716
发布于 2020-06-17 11:44:22
发布于 2020-06-17 11:44:22
3.3K00
代码可运行
举报
文章被收录于专栏:A周立SpringCloudA周立SpringCloud
运行总次数:0
代码可运行

# 面试题:

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

# 核心思想

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

# 代码演示

  • 场景1:run()方法中没有sleep()/wait()等会响应中断的方法。 1.1 线程未处理中断:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 正确停止线程---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毫秒之后停下来。
  • 结果:线程并没有停下来。原因是:我们并未处理线程的中断信号。

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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毫秒之后响应中断,停下来。
  • 结果:线程成功响应中断,提前结束。
  • 总结可得出:线程调用者可以向线程发出中断请求,但是线程中断的权利控制在线程代码的编写者是否响应了你的中断请求。线程代码的编写者比调用者更加了解线程应不应该被停止,何时停止。
  • 场景2:run()方法中存在sleep()/wait()等会响应中断的方法。(响应中断的方法会抛出InterruptedException) 2.1 sleep()在while循环外
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 中断线程-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,打印异常堆栈。
  • 测试:符合预期。

2.2 无法停止的线程:sleep()方法在while循环内。

  • 你预期下面代码的执行结果是怎样的?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 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()响应了中断,打印了异常堆栈。但是线程并没有停下来,而是继续执行。就像什么都没有发生一样。
  • 原因:sleep()在响应了中断之后,清除了线程的中断状态。那么while判断时不知道线程被中断了。
  • 查看sleep()方法的描述:当InterruptedException异常被抛出后,线程的中断状态将被清除。
  • 类似的,查看Object.wait()的方法描述。
  • 类似的会响应中断的方法还有那些?
  • 响应中断的方法总结
    • 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. 传递中断:在方法签名中将中断异常抛出,而不是生吞,交给调用者处理。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 正确停止线程的方式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();
    }
}
  • 结果:
  1. 不想或无法传递时:应该恢复中断(Thread.currentThread().interrupt())
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 正确停止线程的方式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();
    }
}
  • 结果:

# 线程中断的相关方法

  • 预期下面代码的执行结果?
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 线程中断的相关方法
 *
 * @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());
    }
}
  • 结果:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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()会先获取到当前执行这段代码的线程,清除其中断状态,并返回中断状态。
  • 总结:
    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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 IT牧场 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Github利用.gitignore文件忽略不需要上传的文件
在小程序开发过程中,利用gitHub进行项目管理,其中的小程序配置文件会产生冲突,可以使用.gitignore文件配置,在上传gitHub的时候忽略不需要上传的文件。
德顺
2019/11/13
2.6K0
Github利用.gitignore文件忽略不需要上传的文件
Git忽略提交规则 .gitignore文件(上)
在使用Git的过程中,我们喜欢有的文件比如日志,临时文件,编译的中间文件等不要提交到代码仓库,这时就要设置相应的忽略规则,来忽略这些文件的提交。简单来说一个场景:在你使用git add .的时候,遇到了把你不想提交的文件也添加到了缓存中去的情况,比如项目的本地配置信息,如果你上传到Git中去其他人pull下来的时候就会和他本地的配置有冲突,所以这样的个性化配置文件我们一般不把它推送到git服务器中,但是又为了偷懒每次添加缓存的时候都想用git add .而不是手动一个一个文件添加,该怎么办呢?很简单,git为我们提供了一个.gitignore文件只要在这个文件中申明那些文件你不希望添加到git中去,这样当你使用git add .的时候这些文件就会被自动忽略掉。
陈不成i
2021/06/07
7K0
Git忽略规则.gitignore梳理
对于经常使用Git的朋友来说,.gitignore配置一定不会陌生。废话不说多了,接下来就来说说这个.gitignore的使用。 首先要强调一点,这个文件的完整文件名就是".gitignore",注意最前面有个“.”。 一般来说每个Git项目中都需要一个“.gitignore”文件,这个文件的作用就是告诉Git哪些文件不需要添加到版本管理中。实际项目中,很多文件都是不需要版本管理的,比如Python的.pyc文件和一些包含密码的配置文件等等。这个文件的内容是一些规则,Git会根据这些规则来判断是否将文件添加
洗尽了浮华
2018/01/22
1.1K0
Git 的 .gitignore 配置
.gitignore 配置文件用于配置不需要加入版本管理的文件,配置好该文件可以为我们的版本管理带来很大的便利,以下是个人对于配置 .gitignore 的一些心得。
ydymz
2018/10/09
4870
git .gitignore 忽略规则的匹配语法
2)以“#”开头的行都会被 Git 忽略。即#开头的文件标识注释,可以使用反斜杠进行转义;
小蔚
2020/09/07
8.1K0
Git .gitignore文件简介及使用
.gitignore 这个文件的作用就是告诉Git哪些文件不需要添加到版本管理中。实际项目中,很多文件都是不需要版本管理的,比如Python的.pyc文件,Git会根据这个文件里配置的这些规则来判断是否将文件添加到版本控制中。
授客
2019/09/11
1.3K0
Python - Git仓库忽略提交规则 & .gitignore配置
这种方式通过在项目的某个文件夹下定义 .gitignore 文件,在该文件中定义相应的忽略规则,来管理当前文件夹下的文件的Git提交行为。
小菠萝测试笔记
2020/06/09
4.7K0
gitignore用法
下面有些人会遇到加上这个文件,发现忽略的文件还是有上传。 在git库中已存在了这个文件,之前push提交过该文件。 .gitignore文件只对还没有加入版本管理的文件起作用,如果之前已经用git把这些文件纳入了版本库,就不起作用了 解决: 需要在git库中删除该文件,并更新。 然后再次git status查看状态,文件不再显示状态。
2021/11/08
4620
.gitignore文件怎么写,看完这篇文章你就懂了(.gitignore常用语法)
前言 我们在使用git的时候,如果需要忽略某个文件夹,可以在仓库根目录下创建一个.gitignore文件,在.gitignore添加需要忽略的文件和文件夹,那么git就会忽略相应的文件和文件夹,就不会提交和上传这些文件
超级小可爱
2023/05/21
1K0
gitignore文件「建议收藏」
gitignore:使用一个文件指定了上传的黑名单,使我们push的时候把符合该规则的路径都忽略掉
全栈程序员站长
2022/07/20
4970
vue项目打包后提交到git上为什么没有dist这个文件?
vue项目中使用npm run build打包后会生成一个dist文件,使用git推送项目后,发现git上少了一个dist文件,为什么明明本地项目中有这个文件而推上去就没有了呢?
江一铭
2022/06/16
1.3K0
vue项目打包后提交到git上为什么没有dist这个文件?
9 Git gitignore 设置
在Git工作区的根目录下创建一个特殊的.gitignore文件,然后把要忽略的文件名填进去,Git就会自动忽略这些文件。
acc8226
2022/05/17
3820
Settings -> Plugins 原
Free Mybatis Plugins    (*mapper.java-- *mapper.xml)
wuweixiang
2018/08/14
5020
Settings -> Plugins 
                                                                            原
gitignore 使用
在git中如果想忽略掉某个文件,不让这个文件提交到版本库中,可以使用修改根目录中 .gitignore 文件的方法(如无,则需自己手工建立此文件)。这个文件每一行保存了一个匹配的规则例如: # 此为注释 – 将被 Git 忽略 *.a # 忽略所有 .a 结尾的文件 !lib.a # 但 lib.a 除外 /TODO # 仅仅忽略项目根目录下的 TODO 文件,不包括 subdir/TODO build/ # 忽略 build/ 目录下的所有文件 doc/*.t
千往
2018/01/24
6130
.gitignore文件作用
一般来说每个Git项目中都需要一个.gitignore文件,这个文件的作用就是告诉Git哪些文件不需要添加到版本管理中。
陈不成i
2021/05/28
8580
Git 忽略一些文件不加入版本控制
在git中如果想忽略掉某个文件,不让这个文件提交到版本库中,可以使用修改 .gitignore 文件的方法。这个文件每一行保存了一个匹配的规则例如:
全栈程序员站长
2022/07/07
4680
GIT 使用笔记
**文件有三种状态:已提交(committed) / 已修改(modified) / 已暂存(staged). **
小小明童鞋
2019/06/20
4730
不要忽视 .gitignore
有些文件你希望 Git 忽略,不要在你的版本库中跟踪它,这些文件包括许多自动生成的或特定于平台的文件,以及其他本地配置文件,如:
用户8639654
2021/09/15
5770
gitignore文件的作用
早上上传文件的时候,发现有一些密钥不需要增加到版本库中,但是由于每次git status都会提示Untracked files ...并且这些文件并不需要一直提示,会占用相应的需要跟踪的文件位置,导致自己看不到; 所以自己百度了一下,原来根目录一直有一个.gitignore隐藏文件,而这个隐藏文件的作用不止止是可以忽略原来制定的规则,并且也可以根据自己的需要制定自己的规则; 在git中如果想忽略掉某个文件,不让这个文件提交到版本库中,可以使用修改根目录中 .gitignore 文件的方法(如无,则需自己手工
233333
2018/03/07
9230
如何使用.gitignore忽略Git中的文件和目录
通常,在项目上使用Git的工作时,你会希望排除将特定文件或目录推送到远程仓库库中的情况。.gitignore文件可以指定Git应该忽略的未跟踪文件。 在本教程中,我们将说明如何使用.gitignore
入门笔记
2022/06/02
9.5K0
相关推荐
Github利用.gitignore文件忽略不需要上传的文件
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验