Two Phase Termination
在一个线程 T1 中如何“优雅”终止线程 T2?
这里的【优雅】指的是给 T2 一个料理后事的机会。
这里终止过程分成两个阶段:一阶段主要是线程T1向线程T2发送终止指令,二阶段就是线程T2响应终止指令。

1. 错误思路

  • 使用线程对象的 stop() 方法停止线程
    • stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
  • 使用 System.exit(int) 方法停止线程
    • 目的仅是停止一个线程,但这种做法会让整个程序都停止


2. 两阶段终止模式

twophase.png

2.1 利用 isInterrupted

interrupt 可以打断正在执行的线程,无论这个线程是在 sleep,wait,还是正常运行

  1. class TPTInterrupt {
  2. private Thread thread;
  3. public void start(){
  4. thread = new Thread(() -> {
  5. while(true) {
  6. Thread current = Thread.currentThread();
  7. if(current.isInterrupted()) {
  8. log.debug("料理后事");
  9. break;
  10. }
  11. try {
  12. Thread.sleep(1000);
  13. log.debug("将结果保存");
  14. } catch (InterruptedException e) {
  15. current.interrupt();
  16. }
  17. // 执行监控操作
  18. }
  19. },"监控线程");
  20. thread.start();
  21. }
  22. public void stop() {
  23. thread.interrupt();
  24. }
  25. }

调用

  1. TPTInterrupt t = new TPTInterrupt();
  2. t.start();
  3. Thread.sleep(3500);
  4. log.debug("stop");
  5. t.stop();

结果

  1. 11:49:42.915 c.TwoPhaseTermination [监控线程] - 将结果保存
  2. 11:49:43.919 c.TwoPhaseTermination [监控线程] - 将结果保存
  3. 11:49:44.919 c.TwoPhaseTermination [监控线程] - 将结果保存
  4. 11:49:45.413 c.TestTwoPhaseTermination [main] - stop
  5. 11:49:45.413 c.TwoPhaseTermination [监控线程]

2.2 利用停止标记

  1. // 停止标记用 volatile 是为了保证该变量在多个线程之间的可见性
  2. // 我们的例子中,即主线程把它修改为 true 对 t1 线程可见
  3. class TPTVolatile {
  4. private Thread thread; // 采集线程
  5. private volatile boolean stop = false; // 线程终止标识位
  6. //启动采集功能
  7. public void start(){
  8. thread = new Thread(() -> {
  9. while(true) {
  10. Thread current = Thread.currentThread();
  11. if(stop) {
  12. log.debug("料理后事");
  13. break;
  14. }
  15. try {
  16. // 每隔1秒采集、回传一次数据
  17. Thread.sleep(1000);
  18. log.debug("将结果保存");
  19. } catch (InterruptedException e) {
  20. // 重新设置线程中断状态
  21. // 因为打断sleep中的线程会清空打断状态
  22. current.interrupt();
  23. }
  24. // 执行监控操作
  25. }
  26. },"监控线程");
  27. thread.start();
  28. }
  29. public void stop() {
  30. // 设置中断标识位
  31. stop = true;
  32. thread.interrupt();
  33. }
  34. }

调用

  1. TPTVolatile t = new TPTVolatile();
  2. t.start();
  3. Thread.sleep(3500);
  4. log.debug("stop");
  5. t.stop();

结果

  1. 11:54:52.003 c.TPTVolatile [监控线程] - 将结果保存
  2. 11:54:53.006 c.TPTVolatile [监控线程] - 将结果保存
  3. 11:54:54.007 c.TPTVolatile [监控线程] - 将结果保存
  4. 11:54:54.502 c.TestTwoPhaseTermination [main] - stop
  5. 11:54:54.502 c.TPTVolatile [监控线程] - 料理后事

案例

JVM内存监控