介绍
在上一篇文章介绍了线程如何正确启动,接下来就来认识下线程的停止。
线程属于一次性消耗品,正常情况下当线程执行完run()方法之后,线程就会自动结束了。然而在某些特殊场景,如run方法陷入了无限循环、死锁等情形,run()方法就不能正常执行结束并让线程退出了,那么当需要结束线程时,到底应该如何退出线程呢?
通常线程会在什么情况下停止
首先我们先了解线程一般在什么情况下停止:
- 当线程的run()方法所有代码执行完成了
- 当有异常出现,而方法中没有捕获
正确的停止方法:interrupt
当前最常用也是正确的停止线程的方法就是调用线程的interrupt()方法,向线程发出中断信号,线程响应中断之后而停止。
当线程收到中断信号之后如何停止:
- 普通情况下,需要在程序中,自己编写响应中断的语句,让这个语句来结束线程
- 比如线程中判断是否有中断信号了,如果有,就直接到程序末尾,没有就继续执行
- 线程可能处于阻塞状态
- 线程在休眠时,如果收到中断信号,就会抛出异常,这个异常就是处理中断的方式。
- 如果线程在循环的每次迭代中都会休眠一定时间
- 在休眠时,收到中断信号仍然会抛出异常
- 与上面的区别是,循环的条件判断中不需要判断是否收到了中断信号,因为在循环的方法体中,线程会进入休眠状态,休眠收到中断自然就会抛出异常
注意:
- 如果线程在处理了中断信号之后,后面还有程序,那么线程会继续执行后面的程序
- 线程处理中断信号的时候,会把中断标志位还原。
- 线程判断是否收到了中断信号就是通过判断中断标志位的状态而进行的。
实际开发中的两种最佳实践
- 优先选择:传递中断
- 比如在子方法中,线程处于休眠状态,遭遇了中断,需要处理异常,最好的方式就是直接把异常抛出
- 然后一层一层子方法都将异常抛出,直到最顶层的方法,没有再向上的方法了,这个顶层的方法就必须处理这个异常,而且不能抛出了,只能用try/catch的方式处理,这个处理就是用来响应中断请求的。
- 不想或无法传递:恢复中断
- 在子方法无法抛出或者不想抛出异常的时候,就需要在子方法的try/catch语句中的catch中,再次使用Thread.currentThread().interrupted方法,再次重写刚才被处理的中断;这样后续执行中,仍然能检测到刚才发生的中断,后续代码能处理刚才的中断
响应中断的方法总结列表
响应中断:是指这些方法执行的过程中,如果一个中断信号过来了,这些方法是能感知到中断信号的,拥有响应中断的能力
- Object.wait()/wait(long)/wait(long,int);
- Thread.sleep(long)/sleep(long,int);
- Thread.join()/join(long)/join(long,int);
- java.util.concurrent.BlockingQueue.take()/put(E)
- java.util.concurrent.locks.Lock.lockInterruptibly()
- java.util.concurrent.CountDownLatch.await()
- java.util.concurrent.CyclicBarrier.await()
- java.util.concurrent.Exchanger.exchange(V)
- java.nio.channels.InterruptibleChannel相关方法
- java.nio.channels.Selector的相关方法
错误的停止方法
被弃用的stop,suspend和resume方法
为什么弃用Thread.stop()
因为它本质上是不安全的。停止线程会使它解锁它已锁定的所有监视器。 (当ThreadDeath异常在堆栈中向上传播时,监视器将被解锁。)如果先前由这些监视器保护的任何对象处于不一致状态,则其他线程现在可能会以不一致状态查看这些对象。据说这些物体已损坏。当线程对损坏的对象进行操作时,可能会导致任意行为。此行为可能是微妙的,难以检测,或者可能是明显的。与其他未检查的异常不同,ThreadDeath会无声地杀死线程。因此,用户没有警告其程序可能已损坏。在实际损坏发生后的任何时间,甚至未来数小时或数天,腐败都会显现出来。(从官方文档通过google翻译而来)
为什么弃用Thread.resume()
Thread.suspend本质上是容易死锁的。如果目标线程在挂起关键系统资源时在监视器上持有锁,则在恢复目标线程之前,任何线程都无法访问此资源。如果将恢复目标线程的线程尝试在调用 之前锁定此监视器,则会导致死锁。此类死锁通常表现为”冻结”进程。
为什么弃用Thread.suspend()
在官方文档中似乎没有看到关于这个方法弃用的声明,但是在调用的时候能看到已被弃用的标志
官方文档
使用被volatile修饰的关键字作为boolean标记位
使用这种方式作为标记,如果判断成立,进入方法体,方法体内线程休眠,遭遇中断就会抛出异常。但是问题在于,如果当线程处于阻塞状态,即使当标记位更新状态,线程也无法更新状态。
判断是否已经被中断的相关方法
- static boolean interrupted():返回线程的中断状态,然后直接把线程的中断状态清除了,设置为false
- Thread.interrupted()方法的目标是当前线程,而不管本方法来自哪个对象调用的,而只是关心执行这个方法所处的方法块是哪个线程的
- 比如main方法创建A对象,A对象调用interrupted()方法,那么这个interruped方法只是返回的main方法线程的中断状态,而不是返回A对象线程的中断状态
- boolean isinterrupted(): 返回线程的中断状态,但是不会清除线程的中断状态
- Thread.interrupted()的目标对象
参考
- 本文作者: xczll
- 本文链接: https://xczllgit.github.io/2020/03/11/concurrent/2020-03-11-stopThread/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!