聊一聊如何优雅的停下线程,除了这两种还有其他的吗?

简介: 今天主要来聊一聊如何优雅的停下线程

前言

大家好,我是小郭,今天主要来聊一聊如何优雅的停下线程。

在开始之前,我们可以思考一下,如何能够让线程停下?

通过查阅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是不够全面的。方案二则是一种更优的选择。

相关文章
|
6月前
|
Java 开发者
线程的诞生之路:Java多线程创建方法的抉择与智慧
【6月更文挑战第19天】Java多线程编程中,开发者可选择继承Thread类或实现Runnable接口。继承Thread直接但受限于单继承,适合简单场景;实现Runnable更灵活,支持代码复用,适用于如银行转账这类需多线程处理的复杂任务。在资源管理和任务执行控制上,Runnable接口通常更优。
35 0
|
7月前
|
监控 安全 Java
CompletableFuture探秘:解锁Java并发编程的新境界
CompletableFuture探秘:解锁Java并发编程的新境界
237 0
阿里巴巴面试题- - -多线程&并发篇(三十六)
阿里巴巴面试题- - -多线程&并发篇(三十六)
|
缓存 Java 编译器
阿里巴巴面试题- - -多线程&并发篇(三十八)
阿里巴巴面试题- - -多线程&并发篇(三十八)
阿里巴巴面试题- - -多线程&并发篇(三十七)
阿里巴巴面试题- - -多线程&并发篇(三十七)
|
存储 缓存 监控
Java并发编程系列之二线程基础
上篇文章对并发的理论基础进行了回顾,主要是为什么使用多线程、多线程会引发什么问题及引发的原因,和怎么使用Java中的多线程去解决这些问题。
Java并发编程系列之二线程基础
|
缓存 监控 Java
阿里巴巴面试题- - -多线程&并发篇(三十一)
阿里巴巴面试题- - -多线程&并发篇(三十一)
|
Java 开发者
阿里巴巴面试题- - -多线程&并发篇(三十九)
阿里巴巴面试题- - -多线程&并发篇(三十九)
|
缓存 Java
阿里巴巴面试题- - -多线程&并发篇(二十九)
阿里巴巴面试题- - -多线程&并发篇(二十九)
|
安全 Java
阿里巴巴面试题- - -多线程&并发篇(三十五)
阿里巴巴面试题- - -多线程&并发篇(三十五)