如何正确停止线程之错误的停止方法

该篇承接上一篇如何正确停止线程?(一)

错误的停止方法

被弃用的stop,suspend和resume方法

示例一:错误的停止方法:用stop()来停止线程,会导致线程运行一半突然停止,没办法完成一个基本单位的操作(一个连队),会造成脏数据(有的连队多领取少领取装备)。

public class StopThread implements Runnable {

    @Override
    public void run() {
        //模拟指挥军队:一共有5个连队,每个连队10人,以连队为单位,发放武器弹药,叫到号的士兵前去领取
        for (int i = 0; i < 5; i++) {
            System.out.println("连队" + i + "开始领取武器");
            for (int j = 0; j < 10; j++) {
                System.out.println(j);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("连队"+i+"已经领取完毕");
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new StopThread());
        thread.start();
        try {
            Thread.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.stop();
    }
}

打印结果:

连队0开始领取武器
0
1
2
3
4
5
6
7
8
9
连队0已经领取完毕
连队1开始领取武器
0
1
2
3
4
5

stop太强制,会导致原来的逻辑没完整做完就停止了。

用volatile设置boolean标记位

  • 看上去可行

示例二:演示用volatile的局限:part1 看似可行

public class WrongWayVolatile implements Runnable {

    private volatile boolean canceled = false;

    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍数。");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        WrongWayVolatile r = new WrongWayVolatile();
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(5000);
        r.canceled = true;
    }
}

打印结果:

0是100的倍数。
100是100的倍数
...
2700是100的倍数。
3200是100的倍数。

这种情况下通过volitile来中断线程是可行的。

  • 错误原因

示例三:演示用volatile的局限part2 陷入阻塞时,volatile是无法线程的 此例中,生产者的生产速度很快,消费者消费速度慢,所以阻塞队列满了以后,生产者会阻塞,等待消费者进一步消费

生产者:

class Producer implements Runnable {

    public volatile boolean canceled = false;

    BlockingQueue storage;

    public Producer(BlockingQueue storage) {
        this.storage = storage;
    }


    @Override
    public void run() {
        int num = 0;
        try {
            while (num <= 100000 && !canceled) {
                if (num % 100 == 0) {
                    storage.put(num);
                    System.out.println(num + "是100的倍数,被放到仓库中了。");
                }
                num++;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("生产者结束运行");
        }
    }
}

消费者:

public class Consumer {

    BlockingQueue storage;

    public Consumer(BlockingQueue storage) {
        this.storage = storage;
    }

    public boolean needMoreNums() {
        if (Math.random() > 0.95) {
            return false;
        }
        return true;
    }
}

主类:

public class WrongWayVolatileCantStop {

    public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);

        Producer producer = new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take()+"被消费了");
            Thread.sleep(100);
        }
        System.out.println("消费者不需要更多数据了。");

        //一旦消费不需要更多数据了,我们应该让生产者也停下来,但是实际情况
        producer.canceled=true;
        System.out.println(producer.canceled);
    }
}

打印结果:
示例三打印结果
我们发现volatile设置为了true,但是生产者并没有停下来。

为什么用volatile停止线程不够全面?

  • 这种做法是错误的,或者说是不够全面的,在某些情况下虽然可用,但是某些情况下有严重问题

  • 这种方法在《Java并发编程实战》中被明确指出了缺陷,我们一起来看看缺陷在哪里

  • 此方法错误的原因在于,如果我们遇到了线程长时间阻塞(这是一种很常见的情况,例如生产者消费者模式中就存在这样的情况),就没办法及时唤醒它,或者永远都无法唤醒该线程,而 interrupt设计之初就是把wait等长期阻塞作为一种特殊情况考虑在内了,我们应该用 interrupt来停止线程。

  • 修正方式

示例四:用中断来修复刚才的无尽等待问题

public class WrongWayVolatileFixed {

    public static void main(String[] args) throws InterruptedException {

        WrongWayVolatileFixed body = new WrongWayVolatileFixed();
        ArrayBlockingQueue storage = new ArrayBlockingQueue(10);

        Producer producer = body.new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(1000);

        Consumer consumer = body.new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "被消费了");
            Thread.sleep(100);
        }
        System.out.println("消费者不需要更多数据了。");

        producerThread.interrupt();
    }


    class Producer implements Runnable {

        BlockingQueue storage;

        public Producer(BlockingQueue storage) {
            this.storage = storage;
        }


        @Override
        public void run() {
            int num = 0;
            try {
                while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
                    if (num % 100 == 0) {
                        storage.put(num);
                        System.out.println(num + "是100的倍数,被放到仓库中了。");
                    }
                    num++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("生产者结束运行");
            }
        }
    }

    class Consumer {

        BlockingQueue storage;

        public Consumer(BlockingQueue storage) {
            this.storage = storage;
        }

        public boolean needMoreNums() {
            if (Math.random() > 0.95) {
                return false;
            }
            return true;
        }
    }
}

打印结果:

4200是100的倍数,被放到仓库中了。
3300被消费了
4300是100的倍数,被放到仓库中了。
3400被消费了
4400是100的倍数,被放到仓库中了。
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.reportInterruptAfterWait(AbstractQueuedSynchronizer.java:2014)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2048)
	at java.util.concurrent.ArrayBlockingQueue.put(ArrayBlockingQueue.java:353)
	at com.hd.thread.stop.wrong.WrongWayVolatileFixed$Producer.run(WrongWayVolatileFixed.java:50)
	at java.lang.Thread.run(Thread.java:745)
消费者不需要更多数据了。
生产者结束运行

使用interrupt,程序正常中断了。

停止线程相关重要函数解析

判断是否已被中断相关方法

  • static boolean interrupted() 判断当前线程是否被中断,调用后会把中断线程直接设为false,即清除中断标志
  • boolean isInterrupted() 判断当前线程是否被中断,不会清除中断标志
  • Thread.interrupted()的目的对象

示例五:注意Thread.interrupted()方法的目标对象是“当前线程”,而不管本方法来自于哪个对象

public class RightWayInterrupted {

    public static void main(String[] args) throws InterruptedException {
        Thread threadOne = new Thread(() -> {
            for (; ; ) {
            }
        });

        // 启动线程
        threadOne.start();
        //设置中断标志
        threadOne.interrupt();
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        //获取中断标志并重置
        System.out.println("isInterrupted: " + threadOne.interrupted());
        //获取中断标志并重直
        System.out.println("isInterrupted: " + Thread.interrupted());
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        threadOne.join();
        System.out.println("Main thread is over.");
    }
}

打印结果:

isInterrupted: true
isInterrupted: false
isInterrupted: false
isInterrupted: true

interrupted()是静态方法无论是对象调用还是类调用判断的都是主线程的中断标志,所以应该都是false。

面试问题

1.如何停止线程?

  1. 原理:用interrupt来请求、好处
  2. 想停止线程,要请求方、被停止方、子方法被调用方相互配合
  3. 最后再说错误的方法:stop/suspend已经废弃,volatile的boolean无法处理长时间阻塞的情况。

面试题实战:

如何停止线程?

A.用volatile的boolean作为标记来停止

B.用stop()方法让线程停止

C.用Interrupt来请求线程停止

解答

应该选C。

1.原理:用interrupt来请求线程停止而不是强制,好处是安全

2.想停止线程,要请求方、被停止方、子方法被调用方相互配合才行:

  • 作为被停止方:每次循环中或者适时检查中断信号,并且在可能抛出nterrupedEXception的地方处理该中断信号;

  • 请求方:发出中断信

  • 子方法调用方(被线程调用的方法的作者)要注意:优先在方法层面抛出Interruped EXception,或者检查到中断信号时,再次设置中断状态;

3.最后再说错误的方法:stop/suspend已废弃,volatile的boolean无法处理长时间阻塞的情况

2.如何处理不可中断的阻塞

针对特定的情况使用特定的方法,尽可能做到响应中断。

面试题实战:

无法响应中断时如何停止线程?

A.用 interrupt方法来请求停止线程

B.不可中断的阻塞无法处理

C.根据不同的类调用不同的方法

解答

应该选C。

如果线程阻塞是由于调用了wait(),sleep()或join()方法,你可以中断线程,通过抛出 InterruptedException异常来唤醒该线程。

但是对于不能响应 InterruptedException的阻塞,很遗憾,并没有—个通用的解决方但是我们可以利用特定的其它的可以响应中断的方法,比如ReentrantLock.lockInterruptibly(),比如关闭套接字使线程立即返回等方法来达到目的。

答案有很多种,因为有很多原因会造成线程阻塞,所以针对不同情况,唤起的方法也不同。

总结就是说如果不支持响应中断,就要用特定方法来唤起,没有万能药。

发布了112 篇原创文章 · 获赞 303 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_36221788/article/details/102649804