首先对于线程的停止有一个原则需要记住:想要停止一个线程,应该是让该线程知道他需要停止【通过协作的方式】,而不是强制停止。
下面就来说一下如何做到通知线程应该要停止了。

使用interrupt停止线程

  1. package ltd.personalstudy.threadbasic;
  2. /**
  3. * @Author 咖啡杯里的茶
  4. * @date 2020/12/6
  5. */
  6. public class StopThread implements Runnable{
  7. @Override
  8. public void run() {
  9. int count = 0;
  10. // 检查线程的标识位,没有被别的线程中断并且满足业务要求就继续执行
  11. while (!Thread.currentThread().isInterrupted() && count < 1000) {
  12. System.out.println("count=" + count++);
  13. }
  14. }
  15. public static void main(String[] args) throws InterruptedException {
  16. Thread thread = new Thread(new StopThread());
  17. thread.start();
  18. Thread.sleep(5);
  19. thread.interrupt(); // 中断线程
  20. }
  21. }

isInterrupted()不会修改线程中断标识位

sleep过程中能否感知到线程中断

  1. package ltd.personalstudy.threadbasic;
  2. import java.sql.Struct;
  3. /**
  4. * @Author 咖啡杯里的茶
  5. * @date 2020/12/6
  6. */
  7. public class StopThread implements Runnable{
  8. @Override
  9. public void run() {
  10. int count = 0;
  11. while (!Thread.currentThread().isInterrupted() && count < 1000) {
  12. try {
  13. Thread.sleep(1000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. System.out.println("count=" + count++);
  18. }
  19. System.out.println(Thread.currentThread().isInterrupted());
  20. }
  21. public static void main(String[] args) throws InterruptedException {
  22. Thread thread = new Thread(new StopThread());
  23. thread.start();
  24. Thread.sleep(5);
  25. thread.interrupt();
  26. }
  27. }

执行结果
image.png

  • 在sleep、wait等可以让线程进入阻塞的方法使线程休眠了,处于休眠状态的线程被中断后,线程是可以感知到中断信号的,并且会抛出一个InterruptedException,同时会清楚中断信号

    最佳处理方式

    直接抛出异常

    image.png

    再次中断

    image.png

错误的线程停止方法

  1. stop()
    1. 该方法会直接停止线程,不够友好
  2. suspend()
    1. 该方法调用之后线程会挂起,但是在调用resume之前是不会释放锁的,这样容器导致死锁,所以现在也不使用了。
  3. resume()

上面的三个方法以及被java废弃了,使用这三个方法来停止线程是错误的。

使用volatile修饰标记位

可以使用volatile的情形

image.png
如果所示,上面的代码使用volatile修饰的变量来停止线程是可以的,可以的原因就是在while循环里面的代码是没有发生阻塞的,如果在里面的代码发生了阻塞,即使外面修改volatile的标识位,但是由于不会进入下一次循环,所以也是不能停止线程的。

volatile不能正确停止的事例

  1. package ltd.personalstudy.threadbasic;
  2. import java.util.concurrent.ArrayBlockingQueue;
  3. import java.util.concurrent.BlockingQueue;
  4. /**
  5. * @Author 咖啡杯里的茶
  6. * @date 2020/12/6
  7. */
  8. public class StopThread {
  9. public static void main(String[] args) throws InterruptedException {
  10. ArrayBlockingQueue storage = new ArrayBlockingQueue(8);
  11. Producer producer = new Producer(storage);
  12. Thread producerThread = new Thread(producer);
  13. producerThread.start();
  14. Thread.sleep(500);
  15. Consumer consumer = new Consumer(storage);
  16. while (consumer.needMoreNums()) {
  17. System.out.println(consumer.storage.take() + "被消费了");
  18. Thread.sleep(100);
  19. }
  20. System.out.println("消费者不需要更多的数据了");
  21. producer.canceled = true;
  22. System.out.println(producer.canceled);
  23. }
  24. }
  25. class Producer implements Runnable {
  26. public volatile boolean canceled = false;
  27. BlockingQueue storage;
  28. public Producer(BlockingQueue storage) {
  29. this.storage = storage;
  30. }
  31. @Override
  32. public void run() {
  33. int num = 0;
  34. try {
  35. while (!canceled && num < 1000000) {
  36. if (num % 50 == 0) {
  37. System.out.println(num + "是50的倍数,被放到仓库中了");
  38. storage.put(num);
  39. }
  40. num++;
  41. Thread.sleep(1);
  42. }
  43. } catch (InterruptedException e) {
  44. e.printStackTrace();
  45. } finally {
  46. System.out.println("生产者线程执行结束");
  47. }
  48. }
  49. }
  50. class Consumer {
  51. BlockingQueue storage;
  52. public Consumer(BlockingQueue storage) {
  53. this.storage = storage;
  54. }
  55. public boolean needMoreNums() {
  56. if (Math.random() > 0.97) {
  57. return false;
  58. }
  59. return true;
  60. }
  61. }

在上面的代码中生产者和消费者使用了同一个阻塞队列,在生产者的run方法中有这么一段代码
image.png
如果代码在这里面阻塞了,那么即使外面修改了canceled变量,也会因为由于阻塞而不能进入下一次while循环导致不能正确的停止线程。