- 结束一个线程
- 面试题:如何优雅地结束一个线程?
- 上传一个大文件,正在处理费时的计算,如何优雅地结束这个线程?
- 让线程自然而然地运行结束是最优雅的,但是这是做不到的===>不间断运行的服务器,中间出问题,想把他中断掉,好多人都登陆在上面(状态信息等),不能随随便便打断,不能等好多人都执行完了再结束,因为本身就是一个死循环,执行不完
- 如何优雅地终止掉4中的线程,还能够尽量地不丢失中间的状态,怎么才能做到===>上传一个大文件,后台一直在上传,点了取消,怎么取消掉(线程怎么停止)
- 首先不能二话不说,在线程还在跑的时候,直接将他腿打折
SleepHelper帮助类(只是简答地将try……catch……扔到帮助类中)
在演示式的代码中写很多try……catch……,看起来费劲
package com.mashibing.util;
import java.util.concurrent.TimeUnit;
public class SleepHelper {
public static void sleepSeconds(int seconds) {
try {
TimeUnit.SECONDS.sleep(seconds) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
用stop方法结束线程(过时)
- 调线程的stop方法
- 已经过时了,不建议使用
- 这种方法就相当于直接将线程的腿打折
- 不建议使用stop方法的原因:
- 因为容易产生数据不一致的问题
- 太粗暴了,不管线程处于什么状态(跑得快还是跑得慢还是停着的),都会一下子干掉
- 干掉时stop方法会释放所有的锁,而且不会做善后操作
- 要更改两个值,结果更改了一个值之后就被干掉了===>这就造成了数据不一致的问题
- 假如在线程中打开了一些资源,直接stop的话,不会执行关闭资源的操作===>不会做善后操作
package com.mashibing.juc.c_001_00_thread_end;
import com.mashibing.util.SleepHelper;
public class T01_Stop {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true) {
System.out.println("go on");
SleepHelper.sleepSeconds(1);
}
});
t.start();
SleepHelper.sleepSeconds(5);
t.stop();
}
}
suspend和resume方法(过时)
- 与stop方法一样,都是过时的方法
- suspend是暂停的方法(挂起),resume是继续执行的方法(唤醒)
- 过时的原因和stop过时的原因非常类似
- 为什么不建议使用suspend和resume方法
- 暂停的时候假如持有一把锁,这个锁是不会被释放的
- 假如你忘了用resume让线程重新继续,这把锁会永远不会被释放
- 因此容易产生数据不一致的问题(数据不一致的问题应该不会产生因为自己还持有锁,不会被别人访问)和死锁(会发生)的问题
- 所以这两种方法就被废弃了
package com.mashibing.juc.c_001_00_thread_end;
import com.mashibing.util.SleepHelper;
public class T02_Suspend_Resume {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true) {
System.out.println("go on");
SleepHelper.sleepSeconds(1);
}
});
t.start();
SleepHelper.sleepSeconds(5);
t.suspend();
SleepHelper.sleepSeconds(3);
t.resume();
}
}
volatile结束线程
- volatile修饰一个boolean类型的变量
- 使用变量结束循环,进而结束循环(而且为了保证可见性(顺序性?)要使用volatile关键字)
- 该变量为true时,线程就不断地做计算、做上传、……
- 该变量为false时,线程就结束循环将值打出来
- 用这种方式可以让线程结束,但是很难精确控制循环了多少次
- 相对优雅的方式,不依赖于while循环中中间有状态的情况(比如i在能被2整除的时候才跳出循环,否则不跳出循环),volatile还能起到他的作用
- volatile也有局限性:
- 时间上很难控制===>生产者消费者问题,生产者向容器中放产品,放到4个就停止,用volatile停止不太现实;因为用volatile停止时,当在判断volatile变量的时候,在同步的时间段内时间有可能是不固定的;在这段时间内有可能i就多加了几个
- 在线程循环中有wait、recv、accept方法操作时,wait会阻塞在那里,虽然volatile变量变为了false,但是由于阻塞了,不能来到下一次循环,也结束不了
- volatile不依赖中间变量时,比如多传一点文件、少传一点文件这样,还是可以用的
- 在特定场景下的解决方案===>特定场景下是有用的而且用起来也相对方便
package com.mashibing.juc.c_001_00_thread_end;
import com.mashibing.util.SleepHelper;
public class T03_VolatileFlag {
// volatile修饰一个boolean类型的对象
// 该变量为true时,线程就不断地做计算、做上传、……
// 该变量为false时,线程就结束循环将值打出来
private static volatile boolean running = true;
public static void main(String[] args) {
Thread t = new Thread(() -> {
long i = 0L;
while (running) {
//wait recv accept
i++;
}
System.out.println("end and i = " + i); //4168806262 4163032200
});
t.start();
SleepHelper.sleepSeconds(1);
running = false;
}
}
interrupt设定标志位
- 用interrupt设定标志位,在业务逻辑中到合适的时间点要检查这个标记位是不是被设置了,被设置了退出即可
- 与volatile类似,也是一个标志位,但是interrupt是线程自带的标志位,volatile是程序员自己设置的标志位
- 两者有什么区别?哪个更优雅一点?
- interrupt更优雅一点
- volatile在循环中有sleep wait等阻塞类的方法的时候,不能立即结束线程,要等到阻塞结束到达下一次循环才可以结束循环推出线程;而interrupt时,在sleep和wait中处理InterruptedException也可以正确结束这个线程===>interrupt比volatile稍微优雅一点
- 不足:10个元素的容器,用interrupt精确地控制到第五个的时候打断他是很难做到这一点的===>解决方案:必须得业务线程(即生产者线程)和要做打断生产者线程的线程(即执行操作的线程)要做配合才行===>有一道面试题
package com.mashibing.juc.c_001_00_thread_end;
import com.mashibing.util.SleepHelper;
/**
* interrupt设定标志位
*/
public class T04_Interrupt_and_NormalThread {
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (!Thread.interrupted()) {
//sleep wait
}
System.out.println("t1 end!");
});
t.start();
SleepHelper.sleepSeconds(1);
t.interrupt();
}
}
其他方法(大同小异)
- 需要内部线程做一些定时检查,自己每隔多长时间检查一个标志位(其实也是标志位);
- 每经过一次循环检查一次标志位===>实际上就是!Thread.interrupted()
- 不依赖于中间的次数,不依赖于精确的时间的情况下,interrupt和volatile都好用
总结
- 重要程度为中,面试有可能问到
- 难度较低
- 结束线程的方法:
- 自然结束(能自然结束就尽量自然结束)
- stop() suspend() resume()
- volatile标志
- 不适合某些场景(比如还没有同步的时候,线程做了阻塞操作,没有办法循环回去)
- 打断时间也不是特别精确,比如一个阻塞容器,容量为5的时候结束生产者,但是由于volatile同步线程标志位的时间控制不是很精确,有可能生产者还继续生产一段儿时间.
- interrupt() and isInterrupted (比较优雅)
- 也会有打断时间不是特别精确的弊端
- 要做到精确控制就必须让业务线程跟外面要结束他的线程相互配合,要用到锁才能做到精确的控制===>后面有面试题会说