高并发编程-Thread_正确关闭线程的三种方式_thread 关闭-程序员宅基地

技术标签: 结束线程  【Java并发编程】  优雅的方式停止线程  

在这里插入图片描述


概述 stop() Deprecated

通过阅读源码或者官方的API,可以知道 Thread#stop() 方法已经被废弃了。
在这里插入图片描述

大致意思

这种方法本质上是不安全的。

使用Thread.stop停止线程会导致它解锁所有已锁定的监视
如果先前由这些监视器保护的任何对象处于不一致状态,则损坏的对象将对其他线程可见,从而可能导致任意行为。

stop的许多用法应由仅修改某些变量以指示目标线程应停止运行的代码代替。
目标线程应定期检查此变量,如果该变量指示要停止运行,则应按有序方式从其运行方法返回。

如果目标线程等待很长时间(例如,在条件变量上),则应使用中断方法来中断等待

详见: —> Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?.

那该如何正确的终止线程呢? 这里给出几个思路及demo,供大家参考


方式一 设置开关

package com.artisan.test;

public class StopThread_1 {
    

    public static void main(String[] args) {
    

        WorkThread workThread = new WorkThread();
        workThread.start();

        // main线程继续执行业务逻辑  假设运行了3秒
        try {
    
            System.out.println(Thread.currentThread().getName() + " 运行中");
            Thread.sleep(3_000);

            // 假设触发某个条件,需要退出WorkThread线程
            workThread.shutdownThread();

        } catch (InterruptedException e) {
    
            e.printStackTrace();
        }


    }


    static class WorkThread extends Thread {
    

        // 线程内部设置开关 volatile 多线程可见
        private volatile boolean flag = true;

        @Override
        public void run() {
    
            System.out.println(Thread.currentThread().getName() + " working , flag=" + flag);

            // 通过接收flag来决定 终止或运行
            while (flag) {
    
                // do something ......
            }
        }


        private void shutdownThread() {
    
            this.flag = false;
            System.out.println(Thread.currentThread().getName() + " set flag=" + flag);
        }

    }

}


在这里插入图片描述

运行结果:

在这里插入图片描述


方式二 调用interrupt API

package com.artisan.test;

public class StopThread_2 {
    

    public static void main(String[] args) {
    

        WorkThread workThread = new WorkThread("workThread");
        workThread.start();

        try {
    
            // 模拟主线程的业务
            System.out.println(Thread.currentThread().getName() + " working...");
            Thread.sleep(3_000);

            // 假设触发了某种条件,需要中断workThread线程的执行 调用interrupt
            workThread.interrupt();
            System.out.println("workThread interrupt...");

        } catch (InterruptedException e) {
    
            e.printStackTrace();
        }
    }


    static class WorkThread extends Thread {
    

        public WorkThread(String name) {
    
            super(name);
        }

        @Override
        public void run() {
    
            System.out.println(Thread.currentThread().getName() + " working ");
            // 死循环
            while (true) {
    
                // 判断 该线程是否被打断
                if (Thread.interrupted()) {
    
                    System.out.println(Thread.currentThread().getName() + " received  interrupt signal...");
                    // break (break的话  还会执行 assume some logic is here的代码)
                    // 或者
                    // return (return 的话,如果有后面还有代码的话就不会执行后续的代码了)
                    break;
                }
                // assume some logic is here
            }
        }
    }

}


在这里插入图片描述

运行结果:

在这里插入图片描述


方式三 暴力结束线程-> Daemon Thread + interrupt API

我们在前面使用了

高并发编程-Daemon Thread的创建以及使用场景分析

高并发编程-Thread#interrupt用法及源码分析

在Daemon Thread中我们知道: UserThread 结束后, 那在UserThread中设置的Daemon Thread ,JVM不关心守护程序线程是否正在运行,也就是说即使是Daemon Thread 还在运行,只要UserThread结束了,那么Daemon Thread 就一定会退出,这是由JVM保障的。

那提个问题:

1:那我们是不是可以把业务线程设置成Daemon Thread 呢?

2: 假设可以的话,那哪个线程要和Daemon Thread 绑定在一起呢?

3: 和Daemon Thread 绑定在一起该如何结束呢?

针对问题1 —> 可以

针对问题2 —>实例化一个用于创建UserThread的类,用于创建UserThread执行线程. 在这个UserThread执行线程中,实例化一个线程出来,并设置该线程为Daemon Thread,用于执行业务逻辑

针对问题3 —> 这里我们可以借用interrupt的方式来终止和Daemon Thread 绑定在一起的User Thread.


package com.artisan.test;


public class ThreadControl {
    

    //执行线程
    private Thread executeThread;

    // 内存可见的标识符号
    private volatile boolean finished = false;

    public void execute(Runnable task) {
    
        // Step1 创建执行线程 并启动
        executeThread = new Thread(() -> {
    
            // Step2 创建守护线程 用于执行任务
            Thread runner = new Thread(task);
            runner.setDaemon(true);
            // 启动守护线程执行任务(当外层的执行线程结束的时候,JVM会确保将该守护线程也一并关闭)
            runner.start();

            try {
    
                // join到当前线程,该任务完成后,才继续后续的代码,如果未执行完会一直阻塞在这里
                runner.join();
                // runner执行完以后,设置finished为true
                finished = true;
            } catch (InterruptedException e) {
    
                //e.printStackTrace();
            }
        });

        // 启动执行线程
        executeThread.start();
        System.out.println("任务开始执行...");

    }

    /**
     *  该shutdown方法,由创建ThreadControl实例的线程调用
     * @param mills 最大执行时间
     */
    public void shutdown(long mills) {
    
        long shutdownTime = System.currentTimeMillis();

        // 如果任务没有执行完...
        while (!finished) {
    
            //任务超时。即在规定的最大执行时间内未完成,终止该任务
            if (System.currentTimeMillis() - shutdownTime >= mills){
    
                // 调用interrupt方法 ,退出当前执行线程
                executeThread.interrupt();
                System.out.println("任务超时,interrupt该任务!");
                break;
            }

            // 如果没有超时,休眠1毫秒 ,然后再继续进到while循环判断
            try {
    
                Thread.sleep(1);
            } catch (InterruptedException e) {
    
                System.out.println("执行线程被打断");
                break;
            }
        }
        // 恢复初始状态
        finished = false;
    }

    public static void main(String[] args) {
    

        // 测试一 : 任务在规定的最大存活时间内未执行完成
        long start = System.currentTimeMillis();
        ThreadControl ts = new ThreadControl();
        ts.execute(() -> {
    
            while (true) {
    
                //假设死循环,一直运行

            }
        });
        // 最长执行10秒,超过10秒,中断该线程
        ts.shutdown(10_000);
        long end = System.currentTimeMillis();
        System.out.printf("任务被终止,共耗时【%s】", end - start);
        System.out.println("=====================================");




        // 测试二 : 任务执行时间小于规定的最大存活时间
        start = System.currentTimeMillis();
        ThreadControl tc = new ThreadControl();
        // 模拟该任务 5秒执行完成
        tc.execute(() -> {
    
            try {
    
                Thread.sleep(5_000);
            } catch (InterruptedException e) {
    
                e.printStackTrace();
            }
        });
        // 最大允许存活10秒
        tc.shutdown(10_000);
        end = System.currentTimeMillis();
        System.out.printf("任务已结束,共耗时【%s】", end - start);

    }


}




执行结果:

在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yangshangwei/article/details/101174377

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签