JAVA线程停止的最佳实践(一)

JAVA线程停止的最佳实践

1. 错误的停止方式:两种常见错误

2. 正确的停止方式:如何使用interrupt

开始容易,结束难。形容线程再合适不过了。关于线程停止这块,东西很多,众说纷纭,今天就来梳理下,线程停止的最佳实践。

错误的停止方式

首先简要了解下常见的错误停止方式,以及错在哪里。

这里的错误并不是绝对的错误。可能某些情况下,它依旧能够符合程序运行的期望,但并不能很好的处理所有情况。这里把健壮性不够的方法都归到错误的方法。

1. stop()

假设一个场景,开启一个子线程来模拟银行排队取钱,每个人都取1W元。流程是,柜员先把你银行卡的1W元划掉,然后分10次每次给你1000元。

		private static Runnable runnable = () -> {
        try {
            while (true) {
                System.out.println("从银行卡扣掉此人1W元");
                for (int j = 1; j < 11; j++) {
                    System.out.println("给了" + 1000 * j);
                    //等待印钞机印钱
                    Thread.sleep(10);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(1000);
        t.stop();
    }

执行上面的代码最终可能输出

...
给了10000
从银行卡扣掉此人1W元
给了1000
给了2000
线程停止

即可能会出现从卡里扣了1W,却只给人2000的情况。我们期望整个扣钱和取钱的操作是原子性的。但是由于stop()的直接停止,导致我们没有机会去回滚处理异常情况。

错误之处:stop()粗暴停止,不给你善后处理的机会。

Ps. 有人说stop()时不会释放锁,容易造成死锁。其实是错误的,stop()会释放所有的monitor,而另一个方法suspend()则不会。

2. volatile boolean标志位

这种方式也是经常使用的方式,上面直接用stop()不行。那么我们来改用volatile标志位来控制。

    private volatile static boolean running = true;
    private static Runnable runnable = () -> {
        try {
            while (running) {
                System.out.println("从银行卡扣掉此人1W元");
                for (int j = 1; j < 11; j++) {
                    System.out.println("给了" + 1000 * j);
                    //等待印钞机印钱
                    Thread.sleep(10);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程停止");
    };

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(runnable);
        t.start();
        Thread.sleep(1000);
        running = false;
    }

通过这种方式保证了每次while循环的原子性,打印结果也完全正常,完美!

但是我们现在来改动下需求。现在柜员找钱没那么容易了,只sleep(10)找不到钱了,而是需要从印钞机那里取。因此我们定义一个印钞机,作为钱的生产者,而柜员就是钱的消费者。于是代码改动如下,变成了典型的生产者消费者模型。并且我们在生产者和消费者线程停止时打印日志。

Ps. 如果对BlockingQueue不熟悉建议先了解一下。

		//控制柜员取钱的标志位
    private volatile static boolean consuming = true;
    //控制印钞机印钞票的标志位
    private volatile static boolean producing = true;
    //存钱的保险柜
    private static ArrayBlockingQueue<Integer> moneyQueue = new ArrayBlockingQueue<>(10);

    private static Runnable consumerRunnable = () -> {
        try {
            while (consuming) {
                //每次1000,取够1W下一位
                for (int j = 1; j < 11; j++) {
                    moneyQueue.take();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("消费者线程停止");
    };

    private static Runnable producerRunnable = () -> {
        try {
            while (producing) {
                moneyQueue.put(1000);
                //印钞机每10ms生产1000块钱
                Thread.sleep(10);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("生产者线程停止");
    };

    public static void main(String[] args) throws InterruptedException {
        Thread consumerThread = new Thread(consumerRunnable);
        Thread producerThread = new Thread(producerRunnable);
        consumerThread.start();
        producerThread.start();
        Thread.sleep(2000);
        consuming = false;
        producing = false;
    }

运行上面的代码,我们期望设置consumingproducing为false时,两个线程都停止,但是,运行后控制台输出

生产者线程停止

可见消费者的线程并没有停止。

原因就是**当生产者停止后,由于moneyQueue一直为空,会导致消费者永远阻塞在moneyQueue.take()这里,一直在等待生产者印钞票,根本执行不到while (consuming)来结束线程。**所以这时消费者线程永远不会停止。

错误之处:循环内部如果有阻塞方法,可能导致线程永远不会停止。

如何正确停止请看 正确的停止方式:如何使用interrupt

你可能感兴趣的:(java,Android)