本帖最后由 西西子 于 2023-3-23 14:32 编辑
前言今天主要来聊一聊如何优雅的停下线程。 在开始之前,我们可以思考一下,如何能够让线程停下? 通过查阅JDK,我们不难发现Thread为我们提供了一个stop方法,只要使用stop方法,就立即停止线程,但是发现stop()方法被标注为废弃的方法,因为这个方法会强行把执行到一半的线程终止,可能会引发一些数据不一致或者我们没发预估的问题。 除了stop()方法,我能想到的方案还有两个, 方案一:使用volatile标记位,利用其可见性 方案二:调用Thread的方法interrupted
方案一:使用volatile标记位,利用其可见性通过代码我们来看下方案一,这是一个很经典的生产者和消费者模式。 生产者Demo - //生产者
- class Producer implements Runnable {
- public volatile boolean canc = false;
- private Product product;
- Producer(Product product) {
- this.product = product;
- }
- @Override
- public void run() {
- try {
- while (!canc) {
- try {
- //Thread.sleep(1000);
- product.put("iphone6s");
- System.out.println("put:" + Thread.currentThread().getName());
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- } catch (Exception ex) {
- ...
- } finally {
- System.out.println("结束");
- }
- }
- }
复制代码消费者Demo - //消费者
- class Consumer implements Runnable{
- private Product product;
- Consumer(Product product) {
- this.product = product;
- }
- @Override
- public void run() {
- while (Math.random() > 0.9){
- try {
- Thread.sleep(1000);
- product.take("iPhone6s");
- System.out.println("take:"+Thread.currentThread().getName());
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
复制代码调用生产者和消费者 - public static void main(String[] args) {
- ArrayBlockingQueue queue = new ArrayBlockingQueue(1);
- Product product = new Product(queue);
- Producer producer = new Producer(product);
- Consumer consumer = new Consumer(product);
- Thread c1 = new Thread(consumer);
- Thread p1 = new Thread(producer);
- p1.start();
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- c1.start();
- System.out.println("消费者不需要更多数据了。");
- producer.canc = true;
- System.out.println(producer.canc);
- System.out.println(p1.getState());
- }
复制代码
场景一: 我们把消费者和生产者的线程都开起来,生产者生产一个产品,消费者都会消费一个产品,这个时候volatile的值,在下一次的轮询中值已经变成了true,就跳出while循环,线程就停止,这个场景下volatile就适用了。 场景二: 我们将消费者线程不启动,只生产不消费。 理论上我们期待的结果应该也是值变成true,跳出while循环,线程停止。 结果打印: - Put a iphone6s
- put:Thread-2
- 消费者不需要更多数据了。
- valatile的值: true
- 线程状态:WAITING
复制代码根据打印的结果我们会观察到他没有输出结束的语句, 我们看到了生产者生产了产品,valatile也修改了值,但是线程却没有结束, 这主要的原因是因为,生产者执行了product.put("iphone6s"),没有被消费,造成了阻塞,在它唤醒之前, 无法进入下一次的轮询判断。造成了值修改了,却没有做出相应处理。 我们发现在消费的时候,take方法内部会触发唤醒,当检测到线程已经停止,则抛出InterruptedException异常。
开源码说话,可以看到dequeue,唤醒了线程。
- public E take() throws InterruptedException {
- final ReentrantLock lock = this.lock;
- lock.lockInterruptibly();
- try {
- while (count == 0)
- notEmpty.await();
- return dequeue();
- } finally {
- lock.unlock();
- }
- }
- public final void acquireInterruptibly(int arg)
- throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- }
- private E dequeue() {
- ...
- //释放
- notFull.signal();
- return x;
- }
复制代码 二、方案二:调用Thread的方法interrupted
- static class CreateRunable implements Runnable {
- public CreateRunable(int i) {
- this.i = i;
- }
- private int i;
- public int getI() {
- return i;
- }
- public void setI(int i) {
- this.i = i;
- }
- @Override
- public void run() {
- synchronized (this){
- while ( !Thread.currentThread().isInterrupted() ){
- System.out.println("Runable接口,实现线程"+i++);
- }
- }
- }
- }
复制代码- Thread createThread = new Thread(new CreateRunable(0));
- createThread.start();
- Thread.sleep(5);
- createThread.interrupt();
复制代码休眠5毫秒后,该线程检查到了中断信号,就会停止线程。 那如果任务正在休眠状态,线程会如何处理呢
- @Override
- public void run() {
- synchronized (this){
- while ( !Thread.currentThread().isInterrupted() ){
- try {
- Thread.sleep(10000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("Runable接口,实现线程"+i++);
- }
- }
- }
复制代码
抛出异常,同时清除中断状态,线程会继续执行 - Runable接口,实现线程0
- java.lang.InterruptedException: sleep interrupted
- at java.lang.Thread.sleep(Native Method)
- at main.Thread.threadStartThreeWays$CreateRunable.run(threadStartThreeWays.java:48)
- at java.lang.Thread.run(Thread.java:748)
- Runable接口,实现线程1
- Runable接口,实现线程2
- Runable接口,实现线程3
复制代码
总结我们在这里就不说Stop()方法,因为他太暴力了,不够优雅。这里的优雅指的是可以让线程有时间做好收尾工作,避免数据的错乱。 优雅停下线程的方式主要有两种 - 方案一:使用volatile标记位。
- 方案二:调用Thread的方法interrupted。
通过上面的demo案例,我们可以看到使用方案一的volatile,在某一些特殊的场景下,会发生不能关闭线程的情况。 所以volatile是不够全面的。方案二则是一种更优的选择。
|