­

如何正确停止线程

  • 2019 年 11 月 10 日
  • 筆記

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),顶层方法必须处理该异常,遵免了漏掉或者被吞掉的情况,增强了代码的健壮性。