1. 结束一个线程
  2. 面试题:如何优雅地结束一个线程?
    1. 上传一个大文件,正在处理费时的计算,如何优雅地结束这个线程?
  3. 让线程自然而然地运行结束是最优雅的,但是这是做不到的===>不间断运行的服务器,中间出问题,想把他中断掉,好多人都登陆在上面(状态信息等),不能随随便便打断,不能等好多人都执行完了再结束,因为本身就是一个死循环,执行不完
  4. 如何优雅地终止掉4中的线程,还能够尽量地不丢失中间的状态,怎么才能做到===>上传一个大文件,后台一直在上传,点了取消,怎么取消掉(线程怎么停止)
  5. 首先不能二话不说,在线程还在跑的时候,直接将他腿打折

SleepHelper帮助类(只是简答地将try……catch……扔到帮助类中)

在演示式的代码中写很多try……catch……,看起来费劲

  1. package com.mashibing.util;
  2. import java.util.concurrent.TimeUnit;
  3. public class SleepHelper {
  4. public static void sleepSeconds(int seconds) {
  5. try {
  6. TimeUnit.SECONDS.sleep(seconds) ;
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. }


用stop方法结束线程(过时)

  1. 调线程的stop方法
  2. 已经过时了,不建议使用
  3. 这种方法就相当于直接将线程的腿打折
  4. 不建议使用stop方法的原因:
    1. 因为容易产生数据不一致的问题
    2. 太粗暴了,不管线程处于什么状态(跑得快还是跑得慢还是停着的),都会一下子干掉
    3. 干掉时stop方法会释放所有的锁,而且不会做善后操作
    4. 要更改两个值,结果更改了一个值之后就被干掉了===>这就造成了数据不一致的问题
    5. 假如在线程中打开了一些资源,直接stop的话,不会执行关闭资源的操作===>不会做善后操作
  1. package com.mashibing.juc.c_001_00_thread_end;
  2. import com.mashibing.util.SleepHelper;
  3. public class T01_Stop {
  4. public static void main(String[] args) {
  5. Thread t = new Thread(() -> {
  6. while (true) {
  7. System.out.println("go on");
  8. SleepHelper.sleepSeconds(1);
  9. }
  10. });
  11. t.start();
  12. SleepHelper.sleepSeconds(5);
  13. t.stop();
  14. }
  15. }

suspend和resume方法(过时)

  1. 与stop方法一样,都是过时的方法
  2. suspend是暂停的方法(挂起),resume是继续执行的方法(唤醒)
  3. 过时的原因和stop过时的原因非常类似
  4. 为什么不建议使用suspend和resume方法
    1. 暂停的时候假如持有一把锁,这个锁是不会被释放的
    2. 假如你忘了用resume让线程重新继续,这把锁会永远不会被释放
    3. 因此容易产生数据不一致的问题(数据不一致的问题应该不会产生因为自己还持有锁,不会被别人访问)和死锁(会发生)的问题
    4. 所以这两种方法就被废弃了
  1. package com.mashibing.juc.c_001_00_thread_end;
  2. import com.mashibing.util.SleepHelper;
  3. public class T02_Suspend_Resume {
  4. public static void main(String[] args) {
  5. Thread t = new Thread(() -> {
  6. while (true) {
  7. System.out.println("go on");
  8. SleepHelper.sleepSeconds(1);
  9. }
  10. });
  11. t.start();
  12. SleepHelper.sleepSeconds(5);
  13. t.suspend();
  14. SleepHelper.sleepSeconds(3);
  15. t.resume();
  16. }
  17. }

volatile结束线程

  1. volatile修饰一个boolean类型的变量
  2. 使用变量结束循环,进而结束循环(而且为了保证可见性(顺序性?)要使用volatile关键字)
  3. 该变量为true时,线程就不断地做计算、做上传、……
  4. 该变量为false时,线程就结束循环将值打出来
  5. 用这种方式可以让线程结束,但是很难精确控制循环了多少次
  6. 相对优雅的方式,不依赖于while循环中中间有状态的情况(比如i在能被2整除的时候才跳出循环,否则不跳出循环),volatile还能起到他的作用
  7. volatile也有局限性:
    1. 时间上很难控制===>生产者消费者问题,生产者向容器中放产品,放到4个就停止,用volatile停止不太现实;因为用volatile停止时,当在判断volatile变量的时候,在同步的时间段内时间有可能是不固定的;在这段时间内有可能i就多加了几个
    2. 在线程循环中有wait、recv、accept方法操作时,wait会阻塞在那里,虽然volatile变量变为了false,但是由于阻塞了,不能来到下一次循环,也结束不了
  8. volatile不依赖中间变量时,比如多传一点文件、少传一点文件这样,还是可以用的
  9. 在特定场景下的解决方案===>特定场景下是有用的而且用起来也相对方便
  1. package com.mashibing.juc.c_001_00_thread_end;
  2. import com.mashibing.util.SleepHelper;
  3. public class T03_VolatileFlag {
  4. // volatile修饰一个boolean类型的对象
  5. // 该变量为true时,线程就不断地做计算、做上传、……
  6. // 该变量为false时,线程就结束循环将值打出来
  7. private static volatile boolean running = true;
  8. public static void main(String[] args) {
  9. Thread t = new Thread(() -> {
  10. long i = 0L;
  11. while (running) {
  12. //wait recv accept
  13. i++;
  14. }
  15. System.out.println("end and i = " + i); //4168806262 4163032200
  16. });
  17. t.start();
  18. SleepHelper.sleepSeconds(1);
  19. running = false;
  20. }
  21. }

interrupt设定标志位

  1. 用interrupt设定标志位,在业务逻辑中到合适的时间点要检查这个标记位是不是被设置了,被设置了退出即可
  2. 与volatile类似,也是一个标志位,但是interrupt是线程自带的标志位,volatile是程序员自己设置的标志位
  3. 两者有什么区别?哪个更优雅一点?
    1. interrupt更优雅一点
    2. volatile在循环中有sleep wait等阻塞类的方法的时候,不能立即结束线程,要等到阻塞结束到达下一次循环才可以结束循环推出线程;而interrupt时,在sleep和wait中处理InterruptedException也可以正确结束这个线程===>interrupt比volatile稍微优雅一点
  4. 不足:10个元素的容器,用interrupt精确地控制到第五个的时候打断他是很难做到这一点的===>解决方案:必须得业务线程(即生产者线程)和要做打断生产者线程的线程(即执行操作的线程)要做配合才行===>有一道面试题
  1. package com.mashibing.juc.c_001_00_thread_end;
  2. import com.mashibing.util.SleepHelper;
  3. /**
  4. * interrupt设定标志位
  5. */
  6. public class T04_Interrupt_and_NormalThread {
  7. public static void main(String[] args) {
  8. Thread t = new Thread(() -> {
  9. while (!Thread.interrupted()) {
  10. //sleep wait
  11. }
  12. System.out.println("t1 end!");
  13. });
  14. t.start();
  15. SleepHelper.sleepSeconds(1);
  16. t.interrupt();
  17. }
  18. }

其他方法(大同小异)

  1. 需要内部线程做一些定时检查,自己每隔多长时间检查一个标志位(其实也是标志位);
  2. 每经过一次循环检查一次标志位===>实际上就是!Thread.interrupted()
  3. 不依赖于中间的次数,不依赖于精确的时间的情况下,interrupt和volatile都好用

总结

  1. 重要程度为中,面试有可能问到
  2. 难度较低
  3. 结束线程的方法:
    1. 自然结束(能自然结束就尽量自然结束)
    2. stop() suspend() resume()
    3. volatile标志
      1. 不适合某些场景(比如还没有同步的时候,线程做了阻塞操作,没有办法循环回去)
      2. 打断时间也不是特别精确,比如一个阻塞容器,容量为5的时候结束生产者,但是由于volatile同步线程标志位的时间控制不是很精确,有可能生产者还继续生产一段儿时间.
    4. interrupt() and isInterrupted (比较优雅)
      1. 也会有打断时间不是特别精确的弊端
    5. 要做到精确控制就必须让业务线程跟外面要结束他的线程相互配合,要用到锁才能做到精确的控制===>后面有面试题会说