本文概要:
介绍如何去正确停止一个线程
为什么用 volatile 标记的停止方法可能是错误的?—生产者消费者
为什么不强制停止?
你在学习 stop 方法的时候可能会看到,stop 会让直接停止线程.
但是会发生哪些不好的事情呢, 比如说, 我在写入一个文件, 如果线程突然停止了, 文件输入输出流关闭了吗? 再比如银行系统正在处理 A 给 B 的转账, 如果我们突然把他停止了咋办.
所以说, 很多时候即使是基于特殊情况, 我们也希望让线程把关键的步骤走完再停止, 就像谁愿意去处理离职同事之前的屎山代码呢.
正确的做法是通知, 协作
对于 Java 而言, 我们应该去通知, 也就是使用 interrupt, 他起到的是通知的作用, 对于被通知停止的线程, 它拥有自主权, 他会在合适的时候去停止.
这么做的原因上面也大致谈了谈, 某些业务一定要合理的结束才行, 有始有终, 毕竟谁都不希望数据写一半没有了吧, 这会引起很多问题.
如何去停止线程
public class StopThread implements Runnable {
@Override
public void run() {
int count = 0;
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count = " + count++);
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
Thread.sleep(5);
// 这里把开启的那个线程状态设置为 interrupt
thread.interrupt();
}
}
每个线程都有标志位, 当我们在其他线程内, 如果持有此线程的对象, 是可以对他进行中断标志位进行设置, 那个线程在 <运行到>这一行代码的时候判断出状态被中断, 然后我们自己手动去让他跳出循环即可.
阻塞, 等待情况分析
有没有注意到上面的三个字 <运行到> ,那么如果是等待或者是阻塞的情况, 就不会运行这一行代码了啊, 还怎么去让他停止
阻塞和等待代表了什么
阻塞: 没有拿到 synchronized 的锁
等待: sleep, wait, join 等
这些方法都会让线程进入一个无法执行代码的状态, 无法判断标志位, 无法通过我们预留的安全通道逃跑, 我们只能用别的办法了.
阻塞和等待都不是陷入一种"死亡"状态, 他们可以感知信号, 所以 Java 开发者是这么去解决阻塞和等待情况下的停止线程问题, 当我们尝试去设置状态的时候, 他们就会抛出错误.
public class SleepThreadExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
System.out.println("Sleeping for 5 seconds...");
Thread.sleep(5000);
System.out.println("Woke up from sleep.");
} catch (InterruptedException e) {
System.out.println("Thread was interrupted while sleeping.");
// 这里输出一下线程的状态看看是不是被中断了
System.out.println(Thread.currentThread().isInterrupted());
// 这行非常重要
Thread.currentThread().interrupt();
}
});
thread.start();
try {
Thread.sleep(2000);
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面线程在休眠的时候被设置中断标志位了, 这里要注意一点!, sleep 会让线程休眠, 而此时让其中断, 会自动清楚中断信号. 所以说在使用 sleep 的时候, 如果要中断, 一定要注意设置中断标志位. 如果不设置, 那么那个捕获到 try/catch 实际上不会"告诉其他人", 因为中断标志被设置成 false 了.
注意:sleep()方法不会释放锁资源,但是会释放CPU资源。
延申
上面我们使用的方法是, 使用 try/catch 方法去捕捉异常, 如果线程运行的方法是很多个方法, 方法调用方法, 此时该怎么结束.
就比如说我们会调用同事写的代码, 他是用 try/catch 的, 还是用 Exception 的呢?
我们在真实设计的时候, 应该要注意, 线程 run 方法调用的方法是否是 tcy/catch, 因为我们的 run 方法里面也有很多重要的业务, 假如子方法处理的异常, 然后直接抛出运行时异常, 也就是说子方法出错了, 没有告诉 run 方法出错了, run 方法就有可能很多业务还没有处理.
run ()方法, 他不能抛出异常, 只能去通过 try/catch, 而 run ()方法要想控制自己调用的方法, 以及处理可能发生的异常, 最好是能够接收子方法 throw 的异常. 当然如果子方法能够合理的处理异常, 保证异常不会被遗漏, 那么也是可以自己做出处理的, 但是前提是不会影响到 run()的业务处理.
<无论你有没有抛出异常,最重要的应该是,不能去遗漏他,不能屏蔽中断请求>
错误的几种停止方法
比如说 stop (): 会把线程直接停止, 没有给线程足够的时间去保存关键步骤和数据. 想想线程切换的时候, 都会进行线程的上下文的数据保存
suspend (): 不会释放锁, 就是占着茅坑不拉屎, 后面的人只能干等着. 这"河里"吗, 只有当 resumet 的时候才会释放
volatile 用于停止
还记得刚才, 我说 Thread.currentThtread ().interrupt ()会在代码 <运行时>被判断, 然后我们可以手动预留一个安全通道, 让他正确的退出.
volatile 也能用于设置标识位, 但是时什么原理呢? 还是运行时判断, 也就是说阻塞, 等待都不行.
某些情况下, 是可以使用 volatile 来用于停止的, 但是当阻塞的情况下是不适合的.
阻塞的情况, 不止 wait ()和 sleep ()如果是阻塞队列呢?
而合适的情况下, 你可以参考
public void run() {
int count = 0;
while (!Thread.currentThread().isInterrupted() && count < 1000) {
System.out.println("count = " + count++);
}
}
把这里的! Thread.currentThread().isInterrupted()设置成一个 volatile 变量即可
看看不合适的情况
public class Controller {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(8);
Customer customer = new Customer(blockingQueue);
Producer producer = new Producer(blockingQueue);
Thread thread = new Thread(producer);
thread.start();
Thread.sleep(1000);
// 这里的 need 是个一个概率问题,也就是说,假如等到消费者不拿了,这里就退出了,然后进入下面去停止生产者,可是生产者在阻塞队列堵住了,无法检测标志
while (customer.need()){
System.out.println(customer.getStorage().take() + "被消费");
Thread.sleep(100);
}
System.out.println("不需要数据了");
producer.cancer = true;
}
}
public class Producer implements Runnable{
volatile boolean cancer = false;
private BlockingQueue<Integer> storage;
public Producer(BlockingQueue<Integer> storage) {
this.storage = storage;
}
@Override
public void run() {
int nums = 0;
while(nums <= 100000 && !cancer){
if (nums%50 == 0){
try {
// 假如这里满了,然后就会阻塞住,就无法进入下一个循环来检测 cancer
storage.put(nums);
System.out.println("加入到仓库");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
nums++;
}
}
}
public class Customer {
private static BlockingQueue<Integer> storage;
public Customer(BlockingQueue<Integer> storage) {
this.storage = storage;
}
public boolean need(){
if (Math.random() > 0.97){
return false;
}else {
return true;
}
}
public BlockingQueue<Integer> getStorage(){
return storage;
}
}
结果:
…
1000 被消费
加入到仓库
不需要数据了
然后就停止了, 因为生产者阻塞了, 却没有停止. 程序也没有结束