想要启动线程的话,只需调用 Thread类 的start()方法,并在run()方法中定义需要执行的内容即可,灰常滴简单,但如何优雅地停止线程呢?就值得好好思考玩味了。

1. 为什么需要停止线程

通常情况下我们是不会去手动去停止的,而是等待线程自然运行至结束停止,但是在我们实际开发中,会有很多情况中我们是需要提前去手动来停止线程,比如程序中出现异常错误,比如使用者关闭程序等情况中。在这些场景下如果不能很好地停止线程那么就会导致各种问题,所以正确的停止程序是非常的重要的。

正确优雅地停止线程,在这些特殊的业务场景下就会显得格外有价值了。也正是因此,能够想出一个健壮性足够好,并且能够安全处理好各种业务场景下,正确停止线程的方法程序更是非常的重要。但遗憾的是,Java并没有直接给出简单易用且安全优雅的方式来停止线程,这就需要我们想一些办法来曲线救国了。

2. 强行停止线程会怎样?

在我们平时的开发中我们很多时候都不会注意线程是否是健壮的,是否能优雅的停止,很多情况下都是贸然的强制停止正在运行的线程,这样可能会造成一些安全问题,为了避免造成这种损失,我们应该给与线程适当的时间来处理完当前线程的收尾工作, 而不至于影响我们的业务。

对于 Java 而言,最正确的停止线程的方式是使用 interrupt。但 interrupt仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。可能很多同学会疑惑,既然这样那这个存在的意义有什么呢,其实对于 Java 而言,期望程序之间是能够相互通知、协作的管理线程。
在java中有以下3种方法可以终止正在运行的线程:

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  • 使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的方法。
  • 使用interrupt方法中断线程。

比如我们有线程在进行 io 操作时,当程序正在进行写文件操作,这时候接收到终止线程的信号,那么它不会立马停止,它会根据自身业务来判断该如何处理,是将整个文件写入成功后在停止还是不停止等都取决于被通知线程的处理。如果这里立马终止线程就可能造成数据的不完整性,这是我们业务所不希望的结果。

3. 正确停止线程的方式

3.1 interrupt 停止线程

关于 interrupt 的使用我们不在这里过多阐述,其核心就是通过调用线程的 isInterrupt() 方法进而判断中断信号,当线程检测到为 true 时则说明接收到终止信号,此时我们需要做相应的处理。

我们编写一个简单例子来看

public static void main(String[] args) {Thread thread = new Thread(() -> {while (true) {//判断当前线程是否中断if (Thread.currentThread().isInterrupted()) {System.out.println("线程1 接收到中断信息,中断线程...中断标记:" + Thread.currentThread().isInterrupted());//跳出循环,结束线程break;}System.out.println(Thread.currentThread().getName() + "线程正在执行...");}}, "interrupt-1");//启动线程 interrupt-1thread.start();//创建 interrupt-2 线程new Thread(() -> {int i = 0;while (i <20){System.out.println(Thread.currentThread().getName()+"线程正在执行...");if (i == 8){System.out.println("设置线程中断...." );//通知线程1 设置中断通知thread.interrupt();}i ++;try {TimeUnit.MILLISECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}},"interrupt-2").start();}

上述代码相对比较简单,我们创建了两个线程,第一个线程我们其中做了中断信号检测,当接收到中断请求则结束循环,自然的终止线程,在线程二中,我们模拟当执行到 i==8 时通知线程1终止,这种情况下我们可以看到程序自然的进行的终止。

3.2 休眠时中断

这里有个思考:当处于 sleep 时,线程能否感受到中断信号?

对于这一特殊情况,我们可以将上述代码稍微修改即可进行验证,我们将线程1的代码中加入 sleep 同时让睡眠时间加长,让正好线程2通知时线程1还处于睡眠状态,此时观察是否能感受到中断信号。

Thread thread = new Thread(() -> {while (true) {//判断当前线程是否中断if (Thread.currentThread().isInterrupted()) {System.out.println("线程1 接收到中断信息,中断线程...中断标记:" + Thread.currentThread().isInterrupted());Thread.interrupted(); //对线程进行复位,由 true 变成 falseSystem.out.println("经过 Thread.interrupted() 复位后,中断标记:" + Thread.currentThread().isInterrupted());//再次判断是否中断,如果是则退出线程if (Thread.currentThread().isInterrupted()) {break;}break;}System.out.println(Thread.currentThread().getName() + "线程正在执行...");try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}}}, "interrupt-1");//启动线程 interrupt-1thread.start();

我们执行修改后的代码,发现如果 sleep、wait等可以让线程进入阻塞的方法使线程休眠了,而处于休眠中的线程被中断,那么线程是可以感受到中断信号的,并且会抛出一个 InterruptedException异常,同时清除中断信号,将中断标记位设置成 false。这样一来就不用担心长时间休眠中线程感受不到中断了,因为即便线程还在休眠,仍然能够响应中断通知,并抛出异常。

对于线程的停止,最优雅的方式就是通过 interrupt 的方式来实现,如 InterruptedException 时,再次中断设置,让程序能够续继续进行终止操作。不过对于 interrupt 实现线程的终止在实际开发中发现使用的并不是很多,很多都可能喜欢另一种方式,通过标记位。

3.3 用 volatile 标记位的停止方法

关于 volatile 作为标记位的核心就是他的可见性特性,我们通过一个简单代码来看:

public class MarkThreadTest {//定义标记为 使用 volatile 修饰private static volatile  boolean mark = false;@Testpublic void markTest(){new Thread(() -> {//判断标记位来确定是否继续进行while (!mark){try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程执行内容中...");}}).start();System.out.println("这是主线程走起...");try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//10秒后将标记为设置 true 对线程可见。用volatile 修饰mark = true;System.out.println("标记位修改为:"+mark);}}

设置标记,让线程可见进而终止程序,这里我们需要讨论的是,使用 volatile 是真的都是没问题的,上述场景是没问题,但是在一些特殊场景使用 volatile 时是存在问题的,这也是需要注意的!

3.2.1 volatile 修饰标记位不适用的场景

这里我们使用一个生产/消费的模式来实现一个 Demo

public class Producter implements Runnable {//标记是否需要产生数字public static volatile boolean mark = true;BlockingQueue<Integer> numQueue;public Producter(BlockingQueue numQueue){this.numQueue = numQueue;}@Overridepublic void run() {int num = 0;try {while (num < 100000 && mark){//生产数字,加入到队列中if (num % 50 == 0 ){System.out.println(num + " 是50的倍数,加入队列");numQueue.put(num);}num++;}} catch (InterruptedException e) {e.printStackTrace();}finally {System.out.println("生产者运行结束....");}}}

首先,声明了一个生产者 Producer,通过 volatile标记的初始值为 true 的布尔值 mark 来停止线程。而在 run()方法中,while 的判断语句是 num 是否小于 100000 及 mark 是否被标记。while 循环体中判断 num如果是 50 的倍数就放到 numQueue 仓库中,numQueue 是生产者与消费者之间进行通信的存储器,当 num 大于 100000或被通知停止时,会跳出 while 循环并执行 finally 语句块,告诉大家“生产者运行结束”。

public class Consumer implements Runnable{BlockingQueue numQueue;public Consumer(BlockingQueue numQueue){this.numQueue = numQueue;}@Overridepublic void run() {try {while (Math.random() < 0.97){//进行消费System.out.println(numQueue.take()+"被消费了...");;TimeUnit.MILLISECONDS.sleep(100);}} catch (InterruptedException e) {e.printStackTrace();} finally {System.out.println("消费者执行结束...");Producter.mark = false;System.out.println("Producter.mark = "+Producter.mark);}}}

而对于消费者 Consumer,它与生产者共用同一个仓库 numQueue,在 run() 方法中我们通过判断随机数大小来确定是否要继续消费,刚才生产者生产了一些 50 的倍数供消费者使用,消费者是否继续使用数字的判断条件是产生一个随机数并与 0.97 进行比较,大于 0.97 就不再继续使用数字。

public static void main(String[] args) {BlockingQueue queue = new LinkedBlockingQueue(10);Producter producter = new Producter(queue);Consumer consumer = new Consumer(queue);Thread thread = new Thread(producter,"producter-Thread");thread.start();new Thread(consumer,"COnsumer-Thread").start();}

主函数中很简单,创建一个 公共仓库 queue 长度为10,然后传递给两个线程,然后启动两个线程,当我们启动后要注意,我们的消费是有睡眠 100 毫秒,那么这个公共仓库必然会被生产者装满进入阻塞,等待消费。

当消费者不再需要数据,就会将 canceled 的标记位设置为 true,理论上此时生产者会跳出 while 循环,并打印输出“生产者运行结束”。

然而结果却不是我们想象的那样,尽管已经把 Producter.mark设置成 false,但生产者仍然没有停止,这是因为在这种情况下,生产者在执行 numQueue.put(num) 时发生阻塞,在它被叫醒之前是没有办法进入下一次循环判断 Producter.mark的值的,所以在这种情况下用 volatile是没有办法让生产者停下来的,相反如果用 interrupt语句来中断,即使生产者处于阻塞状态,仍然能够感受到中断信号,并做响应处理。

总结

通过上面的介绍我们知道了,线程终止的主要两种方式,一种是 interrupt 一种是volatile ,两种类似的地方都是通过标记来实现的,不过interrupt 是中断信号传递,基于系统层次的,不受阻塞影响,而对于 volatile ,我们是利用其可见性而顶一个标记位标量,但是当出现阻塞等时无法进行及时的通知。

在我们平时的开发中,我们视情况而定,并不是说必须使用 interrupt ,在一般情况下都是可以使用 volatile 的,但是这需要我们精确的掌握其中的场景。

如何优雅的停止一个线程?相关推荐

  1. 线程停止继续_晓龙吊打面试官系列: 如何优雅的停止一个线程

    一.什么时候我们需要中断一个线程 在实际的开发中,有很多场景需要我们中断一个正在运行的线程,就比如: 当我们使用抢票软件时,其中某一个通道已经抢到了火车票,这个时候我们就需要通知其他线程停止工作. 当 ...

  2. java 停止一个线程_Java如何停止一个线程

    线程正常执行完毕,正常结束. 2.监视某些条件,直到某些条件成立,结束线程. class TestMyThread extends Thread { private volatile boolean ...

  3. java 一个线程运行_Java并发(基础知识)—— 创建、运行以及停止一个线程

    在计算机世界,当人们谈到并发时,它的意思是一系列的任务在计算机中同时执行.如果计算机有多个处理器或者多核处理器,那么这个同时性是真实发生的:如果计算机只有一个核心处理器那么就只是表面现象. 现代所有的 ...

  4. 如何停止一个线程?优雅终止和暴力终止对比

    目录 1.使用interrupt()优雅的停止线程分析 (1)线程正常终止情况分析 (2)线程异常终止情况分析 2.使用stop()暴力的终止线程分析 (1)stop()方法终止线程的弊端(已废弃) ...

  5. 如何安全的停止一个线程

    调用stop方法 Stop:中止线程,并且清除监控器锁的信息,但是可能导致 线程安全问题,JDK不建议用. Destroy: JDK未实现该方法. Interrupt(线程中止) Interrupt ...

  6. Java一个线程能否结束另一个永不停止的线程

    在Java中停止一个线程有三种办法 : 1.正常结束执行: 2.发生异常; 3.被其他线程stop(Java官方不建议) 参考:https://docs.oracle.com/javase/8/doc ...

  7. 腾讯面试官:如何停止一个正在运行的线程?我蒙了。。。

    以下文章来源方志朋的博客,回复"666"获面试宝典 停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作.停止一个线程可以用Thread.stop()方法, ...

  8. 面试官:如何停止一个正在运行的线程?我一脸蒙蔽...

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:cnblogs.com/greta/p/5624839.ht ...

  9. 如何停止一个正在运行的线程?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | cnblogs.com/greta/p/562 ...

  10. 腾讯面试官:如何停止一个正在运行的线程?我一脸蒙蔽。。。

    停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作.停止一个线程可以用Thread.stop()方法,但最好不要用它.虽然它确实可以停止一个正在运行的线程,但是这个方法是不安 ...

最新文章

  1. 松果出行CTO朱蓝天做客《智慧Talk》,解开共享电单车产研密码
  2. android界面之美---自定义网络请求进度加载对话框
  3. android 开发 分辨率,Android手机应用开发为适应不同分辨率你应该知道的
  4. stl中Priority Queues(优先队列)的基本用法
  5. 使用Oracle验证外部数据
  6. python传文件给java_python使用简单http协议来传送文件
  7. python中、文件最重要的功能是( )和接收数据_Python基础语法14个知识点大串讲
  8. testng依赖_TestNG依赖关系–DependOnMethods,dependsOnGroups
  9. 微众银行互联网架构首次曝光
  10. 电子技术基础数字部分课后习题答案
  11. cpu渲染测试软件,CPU多核项目大比拼 Corona渲染器测试
  12. 在MATLAB绘制三角形区域上的二元函数图像
  13. ENSP实验——Telnet远程连接
  14. Python模拟数据生成器全代码
  15. gitea 1.16.8 忘记口令的处理思路
  16. 个人免签码支付源码+监控APP【野马资源网】
  17. VMware Workstation Pro虚拟机安装Windows server 2008 r2
  18. nexus 私服 提示磁盘空间不足
  19. 肯耐珂萨2019用户生态峰会 开启HR Paas时代
  20. Java小白修炼手册--第五阶段--SpringBoot框架( day01)

热门文章

  1. 软件系统设计-1-软件设计原则
  2. 计算机408考研经验分享
  3. android版本内存卡,版本等级繁多 教你如何挑选手机内存卡
  4. 通过外挂程序实现SBO中的价格控制策略
  5. 三网融合方案通过 一台机器打电话看电视上网
  6. 单片机c语言设计奥运五环,用C语言程序来设计奥运五环图案
  7. 微信内置浏览器是什么?
  8. 完美发布带摘要的dedecms幻灯片代码
  9. 烧毁DC/DC电路问题
  10. IDC发布2021年中国云计算10大预测;Docker 桌面为 M1 推出技术预览版