错误停止线程之volatile设置boolean标记位(慕课网 -> 线程八大核心+Java并发底层原理精讲 掌握企业级并发问题解决方案)

如何停止线程?

A. 用volatile的boolean作为标记来停止
B. 用stop()方法让线程停止
C. 用interrupt来请求线程停止

解答:
应该选C。

原理:用interrupt来请求线程停止而不是强制,好处是安全。
想停止线程,要请求方、被停止方、子方法被调用方相互配合才行:
a) 作为被停止方:每次循环中或者适时检查中断信号,并且在可能抛出InterrupedException的地方处理该中断信号;
b) 请求方:发出中断信号;
c) 子方法调用方(被线程调用的方法的作者)要注意:优先在方法层面抛出InterrupedException,或者检查到中断信号时,再次设置中断状态;
最后再说错误的方法:stop/suspend已废弃,volatile的boolean无法处理长时间阻塞的情况
 

错误停止线程之volatile设置boolean标记位

从三步来展开:
1、看似可行(部分情况适用)
2、错误之处
3、修正方案

一、看似可行

package threadcoreknowledge.stopthreads.volatiledemo;

/**
 * 描述:演示 volatile的局限性,part1看似可行
 */

public class WrongWayVolatile implements Runnable{

    public volatile boolean canceled = false;

    @Override
    public void run() {
        int num = 0;
        try {
            while(num <= 10000 && !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) {
        WrongWayVolatile r = new WrongWayVolatile();
        Thread thread = new Thread(r);
        thread.start();
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        r.canceled = true;
    }
}

二、错误之处

详述示例代码整体思路:

我们首先有一个生产者Producer和一个消费者Consumer,生产者负责生产(代码是循环num++,把是100的倍数加入到一个阻塞队列BlockingQueue),BlockingQueue的容量初始化设置为10,当队列满了之后(就是等于10),生产者会阻塞,也就是Producer的run()方法的while循环中的storage.put(num)会进入wait状态阻塞,这时必须要等消费者Consumer拿num数据,就是storage.take(num),生产者才能继续往阻塞队列BlockingQueue加num,也就是storage.put(num)这步才会执行走下去。然后就是我们设置生产者Producer的生产速度很快,消费者Consumer消费速度慢Thread.sleep(100);,目的就是为了让生产者Producer大多数情况处于一个阻塞状态,最后就是设置一个needMoreNums()方法,方法目的也很简单,就是让消费者在某一时刻说:我不需要更多数据了,就是消费者Consumer不需要数据了,那对应的,这时生产者Producer也就应该停止生产了,也就是我们代码编写者此时应该请求中断生产者Producer的线程,然后我们就通过volatile设置boolean标记位的方式中断线程,看看会发生什么。。。

package threadcoreknowledge.stopthreads.volatiledemo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * 描述:  演示用volatile的局限part2
 * 陷入阻塞时,volatile是无法线程的
 * 此例中,生产者的生产速度很快,消费者消费速度慢,
 * 所以阻塞队列满了以后,生产者会阻塞,等待消费者进一
 * 步消费
 */
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);
    }
}

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("生产者停止运行");
        }
    }
}

class Consumer{

    BlockingQueue storage;

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

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

/**
 * 为什么用volatile停止线程不够全面
 *
 * 解答:这种做法是错误的,或者说是不够全面的,在某些情况下虽然可用,但是某些情况下有严重问题。
 * 这种方法在《Java并发编程实战》中被明确指出了缺陷,我们一起来看看缺陷在哪里:
 * 此方法错误的原因在于,如果我们遇到了线程长时间阻塞(这是一种很常见的情况,例如生产者消费者模式中就存在这样的情况),
 * 就没办法及时唤醒它,或者永远都无法唤醒该线程,而interrupt设计之初就是把wait等长期阻塞作为一种特殊情况考虑在内了,
 * 我们应该用interrupt思维来停止线程。
 * */

打印结果
在这里插入图片描述这说明了已经把volatile设置的标记位canceled设置为true,但神奇地是Producer的线程并没有被停止,而导致线程没有被停止原因是:
在这里插入图片描述三、修正方案
1、改用interrupt()中断
2、把Producer 和 Consumer改成内部类(没什么原因,就是感受一下内部类用法)

下面直接贴代码:

package threadcoreknowledge.stopthreads.volatiledemo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

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();//改用interrupt()中断
    }

    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 && !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;
        }

    }
}



关于!Thread.currentThread().isInterrupted()是否多余的问题:
在这里插入图片描述

 

无法响应中断时如何停止线程?(处理不可中断的阻塞)

A. 用interrupt方法来请求停止线程
B. 不可中断的阻塞无法处理
C. 根据不同的类调用不同的方法

解答:
应该选C。
如果线程阻塞是由于调用了 wait(),sleep() 或 join() 方法,你可以中断线程,通过抛出 InterruptedException 异常来唤醒该线程。
但是对于不能响应InterruptedException的阻塞,很遗憾,并没有一个通用的解决方案。
但是我们可以利用特定的其它的可以响应中断的方法,比如ReentrantLock.lockInterruptibly(),比如关闭套接字使线程立即返回等方法来达到目的。
答案有很多种,因为有很多原因会造成线程阻塞,所以针对不同情况,唤起的方法也不同。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值