1, 原理介绍:
使用 interrupt 来通知, 而不是强制.
在 Java 中, 最好的停止线程的方式是使用中断 Interrupt, 但是这仅仅是会通知到被终止的线程 "你该停止运行了", 被终止的线程自身拥有决定权(决定是否, 以及何时停止), 这依赖于请求停止方和被停止方都遵守一种约定好的编码规范.
任务和线程的启动很容易. 在大多数时候, 我们都会让它们运行直到结東, 或者让它们自行停止. 然而有时候我们希望提前结東任务或线程或许是因为用户取消了操作, 或者服务要被快速关闭, 或者是运行超时或出错了. 要使任务和线程能安全, 快速, 可靠地停止下来, 并不是一件容易的事. Java 没有提供任何机制来安全地终止线程. 但它提供了中断( Interruption 这是一种协作机制, 能够使一个线程终止另一个线程的当前工作).
这种协作式的方法是必要的, 我们很少希望某个任务, 线程或服务立即停止, 因为这种立即停止会使共享的数据结构处于不一致的状态. 相反, 在编写任务和服务时可以使用一种协作的方式: 当需要停止时, 它们首先会清除当前正在执行的工作, 然后再结束. 这提供了更好的灵活性, 因为任务本身的代码比发出取消请求的代码更清楚如何执行清除工作.
生命周期结束 (End-of-Lifecycle) 的问题会使任务, 服务以及程序的设计和实现等过程变得复杂而这个在程序设计中非常重要的要素却经常被忽略. 一个在行为良好的软件与勉强运的软件之间的最主要区别就是, 行为良好的软件能很完善地处理失败, 关闭和取消等过程.
2, 如何正确停止线程
线程通常在什么情况停止普通情况.
执行完所有代码
有异常未捕获.
3, 使用 interrupt 停止的几种情况.
最普遍的情况
- /**
- * @data 2019/11/9 - 下午 8:07
- * 描述: run 方法内没有 sleep 或 wait 方法时, 停止线程
- */
- public class RightWayStopThreadWithoutSleep implements Runnable{
- @Override
- public void run() {
- int num = 0;
- while(num<=Integer.MAX_VALUE/2 && !Thread.currentThread().isInterrupted()){
- 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 RightWayStopThreadWithoutSleep());
- thread.start();
- thread.sleep(1000);
- thread.interrupt();
- }
- }
当停止线程遇到线程阻塞
- /**
- * @data 2019/11/9 - 下午 8:07
- * 描述: 带有 sleep 的中断线程的方法
- */
- public class RightWayStopThreadWithSleep {
- public static void main(String[] args) throws InterruptedException {
- Runnable runnable = ()->{
- int num = 0;
- try {
- while (num<=300 && !Thread.currentThread().isInterrupted()){
- if(num%100 == 0)
- System.out.println(num+"是 100 的倍数");
- num++;
- }
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- };
- Thread thread = new Thread(runnable);
- thread.start();
- Thread.sleep(500);
- thread.interrupt();
- }
- }
结果:
0 是 100 的倍数
100 是 100 的倍数
200 是 100 的倍数
300 是 100 的倍数
- java.lang.InterruptedException: sleep interrupted
- at java.base/java.lang.Thread.sleep(Native Method)
- at threadcoreknowledge.stopthreads.RightWayStopThreadWithSleep.lambda$main$0(RightWayStopThreadWithSleep.java:18)
- at java.base/java.lang.Thread.run(Thread.java:844)
sleep,wait 等一些方法使线程阻塞, 当线程收到通知 interrupt 时候, 这些方法处理该通知的方法是抛出 InterruptedException 异常.
如果线程每次迭代后都阻塞
- /**
- * @data 2019/11/10 - 上午 9:13
- * 描述: 如果每次执行过程中, 每次循环中都调用 sleep 或 wait 等方法时, 那么不需要再每次迭代过程中检查是否已中断.
- */
- public class RightWayStopTHreadWithSleepEveryLoop {
- public static void main(String[] args) throws InterruptedException {
- Runnable runnable = ()->{
- int num = 0;
- try {
- while (num<=10000){
- if(num%100 == 0)
- System.out.println(num+"是 100 的倍数");
- Thread.sleep(10);
- num++;
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- };
- Thread thread = new Thread(runnable);
- thread.start();
- Thread.sleep(5000);
- thread.interrupt();
- }
- }
在循环过程中, CPU 运行速度快, 大部分时间都熬在使其阻塞的方法中, 所以没必要每次迭代检查是否已中断 -(Thread.currentThread().isInterrupted())
4, 如果 while 里面放 try/catch, 会导致中断失效
- /**
- * @data 2019/11/10 - 上午 9:24
- * 描述: 如果 while 里面放 try/catch, 会导致中断失效
- */
- public class CantInterrupt {
- public static void main(String[] args) throws InterruptedException {
- Runnable runnable = ()->{
- int num = 0;
- while(num<10000&& !Thread.currentThread().isInterrupted()){
- if(num%100 == 0){
- System.out.println(num+"是 100 的倍数");
- }
- num++;
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- };
- Thread thread = new Thread(runnable);
- thread.start();
- Thread.sleep(5000);
- thread.interrupt();
- }
- }
结果:
0 是 100 的倍数
100 是 100 的倍数
200 是 100 的倍数
300 是 100 的倍数
400 是 100 的倍数
- java.lang.InterruptedException: sleep interrupted
- at java.base/java.lang.Thread.sleep(Native Method)
- at threadcoreknowledge.stopthreads.CantInterrupt.lambda$main$0(CantInterrupt.java:17)
- at java.base/java.lang.Thread.run(Thread.java:844)
500 是 100 的倍数
600 是 100 的倍数
700 是 100 的倍数
即使加了检查是否已中断, 但程序抛出了异常后 while 里面的内容依然执行, 这是因为当 sleep,wait 等函数阻塞线程后, 会将该线程的阻塞标志清除, 这就导致即使通知终端信号给线程了, 线程检测不出
5, 实际开发中处理终端的最佳方法:
- /**
- * @data 2019/11/10 - 上午 9:41
- * 描述: 最佳实践: catch 了 InterruptedExcetion 只有的优先选择: 在方法签名中抛出异常
- * 那么在 run()就会强制 try/catch
- */
- public class RightWayStopTHreadInProd implements Runnable{
- @Override
- public void run() {
- while (true){
- try {
- System.out.println("go");
- throwInMethod();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- private void throwInMethod() throws InterruptedException {
- Thread.sleep(2000);
- }
- public static void main(String[] args) throws InterruptedException {
- Thread thread = new Thread(new RightWayStopTHreadInProd());
- thread.start();
- Thread.sleep(1000);
- thread.interrupt();
- }
- }
原因:
优先选择在方法上抛出异常.
用 throws Interruptedexception 标记你的方法, 不采用 ty 语句块捕获异常, 以便于该异常可以传递到顶层, 让 run 方法可以捕获这一异常, 例如:
由于 run 方法内无法抛出 checked Exception(只能用 try catch), 顶层方法必须处理该异常, 遵免了漏掉或者被吞掉的情况, 增强了代码的健壮性.
来源: https://www.cnblogs.com/zitai/p/11828998.html