停止线程原则

java没有提供任何机制来安全的中止线程,但是提供了中断(Intertuption)这种协作机制,使用中断interrupt通知需要停止的线程,被终止线程自身拥有决定权(决定是否以及何时停止)。被停止线程本身更清楚在何时停止更为合适,外部强行停止可能使数据处于不一致的情况,使用中断协作方式,需要停止时首先清楚当前正在执行的任务,然后再结束。


实践

1、停止普通线程

被停止线程需要监测线程中断状态

  1. public static void main(String[] args) {
  2. Thread thread = new Thread(() ->{
  3. int num = 0;
  4. while (!Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE / 2){
  5. if(num % 10000 == 0){
  6. System.out.println(num + " 为10000的倍数");
  7. }
  8. num ++;
  9. }
  10. System.out.println("线程运行完毕");
  11. });
  12. thread.start();
  13. try {
  14. Thread.sleep(2000);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. thread.interrupt();
  19. }

2、停止阻塞线程

2.1 停止普通阻塞线程

  1. /**
  2. * @Author: zhangjx
  3. * @Date: 2020/1/14 16:27
  4. * 停止sleep线程
  5. */
  6. public class RightWayStopThreadWithSleep {
  7. public static void main(String[] args) {
  8. Thread thread = new Thread(() ->{
  9. int num = 0;
  10. while (!Thread.currentThread().isInterrupted() && num <= 300){
  11. if(num % 100 == 0){
  12. System.out.println(num + " 为100的倍数");
  13. }
  14. num ++;
  15. }
  16. try {
  17. Thread.sleep(1000);
  18. } catch (InterruptedException e) {
  19. System.out.println("线程被中断");
  20. }
  21. System.out.println("线程运行完毕");
  22. });
  23. thread.start();
  24. try {
  25. Thread.sleep(500);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. thread.interrupt();
  30. }
  31. }

2.1 循环阻塞响应中断

  1. /**
  2. * @Author: zhangjx
  3. * @Date: 2020/1/14 16:34
  4. * 执行过程中每次循环都会阻塞(sleep或wait) 则不需要每次循环都检测中断,sleep中会检测中断
  5. */
  6. public class RightWayStopThreadWithSleepEveryLoop {
  7. public static void main(String[] args) {
  8. Thread thread = new Thread(() ->{
  9. int num = 0;
  10. try{
  11. while ( num <= 10000){
  12. if(num % 100 == 0){
  13. System.out.println(num + " 为100的倍数");
  14. }
  15. num ++;
  16. Thread.sleep(10);
  17. }
  18. } catch (InterruptedException e) {
  19. System.out.println("线程被中断");
  20. }
  21. System.out.println("线程运行完毕");
  22. });
  23. thread.start();
  24. try {
  25. Thread.sleep(5000);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. thread.interrupt();
  30. }
  31. }

2.3 while中捕获中断

sleep时已经响应过一次中断,中断标记位被清除,无法判定被中断

  1. public static void main(String[] args) {
  2. Thread thread = new Thread(() ->{
  3. int num = 0;
  4. while ( num <= 10000 && !Thread.currentThread().isInterrupted()){
  5. if(num % 100 == 0){
  6. System.out.println(num + " 为100的倍数");
  7. }
  8. num ++;
  9. try{
  10. Thread.sleep(10);
  11. } catch (InterruptedException e) {
  12. System.out.println("线程被中断");
  13. }
  14. }
  15. System.out.println("线程运行完毕");
  16. });
  17. thread.start();
  18. try {
  19. Thread.sleep(5000);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. thread.interrupt();
  24. }

解决方案:

  • 方法1 : while中不要try catch InterruptedException异常,try catch 直接包裹整个while
  • 方法2 : while中try catch InterruptedException异常,使用break,跳出while
  • 方法3:传递中断,如果调用其他方法,优先选择在方法上抛出异常,不要使用try catch 捕获异常,以便异常能传到顶层,让run方法可以捕捉这一异常,不要让异常被吃掉。
  1. public static void main(String[] args) {
  2. Thread thread = new Thread(() ->{
  3. int num = 0;
  4. while ( true){
  5. try {
  6. throwInMeyhod();
  7. } catch (InterruptedException e) {
  8. System.out.println("线程中断");
  9. break;
  10. }
  11. }
  12. System.out.println("线程运行完毕");
  13. });
  14. thread.start();
  15. try {
  16. Thread.sleep(5000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. thread.interrupt();
  21. }
  22. private static void throwInMethod() throws InterruptedException {
  23. Thread.sleep(1000);
  24. }
  • 方法4:恢复中断 catch中捕获处理,重新打上中断标记
  1. public static void main(String[] args) {
  2. Thread thread = new Thread(() ->{
  3. int num = 0;
  4. while ( true ){
  5. if(!Thread.currentThread().isInterrupted()){
  6. System.out.println("线程被中断");
  7. break;
  8. }
  9. System.out.println("hello");
  10. throwInMeyhod();
  11. }
  12. System.out.println("线程运行完毕");
  13. });
  14. thread.start();
  15. try {
  16. Thread.sleep(1000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. thread.interrupt();
  21. }
  22. private static void throwInMeyhod() {
  23. try {
  24. Thread.sleep(1000);
  25. } catch (InterruptedException e) {
  26. Thread.currentThread().interrupt();
  27. }
  28. }

2.4 响应中断的方法总结列表

image.png
image.png

2.5 错误停止线程的方法

1、被弃用的stop,suspends和resume

stop本质不够安全
suspends和resume 容易死锁

2、volatile关键字设置标记位

部分情况能够实现线程停止,但是如果在线程被阻塞的情况下无法实现

  • 无法停止阻塞时的中断,而interrupt设计时已经将长期阻塞作为特殊情况
  1. package com.imooc.thread_demo.stopthreads.volatiledemo;
  2. import java.util.concurrent.ArrayBlockingQueue;
  3. import java.util.concurrent.BlockingQueue;
  4. /**
  5. * @Author: zhangjx
  6. * @Date: 2020/8/9 15:11
  7. * @Description: 陷入阻塞中,volatile无法停止线程
  8. */
  9. public class WrongVolatileCantStop {
  10. public static void main(String[] args) throws InterruptedException {
  11. BlockingQueue queue = new ArrayBlockingQueue(10);
  12. Producer producer = new Producer(queue);
  13. Consumer consumer = new Consumer(queue);
  14. Thread producerThread = new Thread(producer);
  15. producerThread.start();
  16. /**
  17. * 1、当主线程睡眠1000毫秒时 此时阻塞队列满,生产者被阻塞在queue.put(num)处,此时volatile无法停止线程
  18. * 2、当主线程睡眠500毫秒时 生产者未被阻塞 到while处判断stop为true则停止
  19. */
  20. Thread.sleep(500);
  21. while(consumer.needProducer()){
  22. System.out.println("数据 " + consumer.queue.take() + "已被消费!");
  23. Thread.sleep(100);
  24. }
  25. System.out.println("消费者不需要更多数据!");
  26. producer.stop = true;
  27. }
  28. }
  29. class Producer implements Runnable{
  30. public volatile boolean stop = false;
  31. private BlockingQueue queue;
  32. @Override
  33. public void run() {
  34. int num = 0;
  35. try{
  36. while (!stop && num <= Integer.MAX_VALUE / 2){
  37. if(num % 100 == 0){
  38. queue.put(num);
  39. System.out.println(num + " 为100的倍数");
  40. }
  41. Thread.sleep(1);
  42. num ++;
  43. }
  44. }catch (Exception e){
  45. e.printStackTrace();
  46. }finally {
  47. System.out.println("生产者运行完毕!");
  48. }
  49. }
  50. public Producer(BlockingQueue queue) {
  51. this.queue = queue;
  52. }
  53. }
  54. class Consumer{
  55. public BlockingQueue queue;
  56. public Consumer(BlockingQueue queue) {
  57. this.queue = queue;
  58. }
  59. boolean needProducer(){
  60. return false;
  61. }
  62. }

3、intertupt相关方法辨析

  • static boolean interrupted()

检测当前线程是否中断,并清除中断位,该方法关心的是执行该方法的线程

  • boolean isInterrupted()

    1. 检测线程Thread对象是否中断,不清除中断位