目录

1、概念

1.1 什么是阻塞队列

1.2 特性 

2、生产者消费者模型 

 2.1 解耦合

2.2 削峰填谷 

 2.3 模型实现

3、阻塞队列  方法及实现

3.1 方法

3.2 核心 

3.3 实现 

3.3.1 普通队列

 3.3.2 加上阻塞


1、概念

1.1 什么是阻塞队列

从名字上 可以看出,它是队列的一种,那肯定是 先进先出(FIFO)的数据结构

1.2 特性 

  1. 阻塞添加:当阻塞队列是满时,继续 添加的操作将被阻塞,直到被其他线程从队列取走元素
  2. 阻塞移除:当阻塞队列是空时,继续 获取的操作将被阻塞,直到被其他线程从队列添加元素

java jstack分析线程阻塞_java

在多线程中,多线程之间进行数据交互,可以使用阻塞队列简化代码实现

应用场景:

  1. 线程池的底层存储
  2. 生产者消费者模型

下面详细讲解 生产者消费者模型

2、生产者消费者模型 

这是一种常用的并发式编程模型,用于解决生产者和消费者之间的数据传输和同步问题

组成:

  1. 生产者:负责生成数据并将其存储在共享数据结构中(阻塞队列)
  2. 消费者:负责从共享数据结构中获取并进行处理
  3. 共享数据结构:一般为阻塞队列实现

生产者往阻塞队列中插入数据,消费者从阻塞队列取出数据,队列的阻塞性可以实现生产者和消费者之间的同步和互斥

 生活中的例子 擀饺子皮(生产者)、包饺子(消费者),放饺子皮(阻塞队列)

 2.1 解耦合

可以让上下游模块之间,进行更好的“解耦合”

耦合:两个或多组模块之间的依赖关系

 问题:高耦合

java jstack分析线程阻塞_开发语言_02

解决: 引入阻塞队列,作为数据传输点

java jstack分析线程阻塞_java jstack分析线程阻塞_03

2.2 削峰填谷 

问题:传输的数据、速度直接影响,超出限度可能出现问题

java jstack分析线程阻塞_开发语言_04

解决:削峰填谷 

java jstack分析线程阻塞_开发语言_05

 2.3 模型实现

public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> bq = new LinkedBlockingQueue<>();
        //消费者
        Thread consumer = new Thread(()->{
            while (true){
                try {
                    int value = bq.take();
                    System.out.println("消费元素:"+value);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        consumer.start();
        //生产者
        Thread producer = new Thread(()->{
            int value = 0;
            while (true){
                try {
                    System.out.println("生产元素:"+value);
                    bq.put(value);
                    value++;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //生产一个元素 消费一个元素
        producer.start();
    }

java jstack分析线程阻塞_开发语言_06

3、阻塞队列  方法及实现

3.1 方法

java jstack分析线程阻塞_开发语言_07

方法类型

抛出异常

返会布尔

阻塞

插入

add()

offer()

put()

取出

remove()

poll()

take()

队首

element()

peek()

注意:

  1. BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.
  2. put 方法用于阻塞式的入队列, take 用于阻塞式的出队列
  3. BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.

3.2 核心 

 阻塞队列核心 两个方法 put() 和 take()

public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> bq1 = new LinkedBlockingQueue<>();
        //阻塞队列核心 两个方法  put() 和 take()
        //put() 阻塞放入
        bq1.put(1);
        bq1.put(2);
        bq1.put(3);
        bq1.put(4);
        bq1.put(5);
        //take() 取出
        System.out.println(bq1.take());
        System.out.println(bq1.take());
        System.out.println(bq1.take());
        System.out.println(bq1.take());
        System.out.println(bq1.take());
        //已经全部取出
        //再取 就进入阻塞等待  
        System.out.println(bq1.take());
    }
}

java jstack分析线程阻塞_阻塞队列_08

3.3 实现 

步骤:

  1. 先实现一个普通队列
  2. 加上阻塞功能
  3. 加上线程安全

3.3.1 普通队列

 实现循环 单向队列,取出和存放

class MyBlockingQueue{
    //数组  存储数据
    private  int[] elem = new int[1000];
    //头尾
    private int head = 0;
    private int tail = 0;
    //有效数据
    private int useSize = 0;

    //put  添加
    public void put(int value){
        //判满
        if(useSize == elem.length){
            return;
        }
        //添加  放到尾坐标上
        elem[tail] = value;
        tail++;
        //放到最后 从头开始
        if(tail == elem.length){
            tail = 0;
        }
        useSize++;
    }
    //take 移除
    public int put(){
        //判空
        if(useSize == 0){
            return -1;
        }
        //从头 取出
        int value = elem[head];
        head++;
        //取到尾 从头开始
        if(head == elem.length){
            head = 0;
        }
        useSize--;
        return value;
    }
}

 3.3.2 加上阻塞

  1. 加阻塞 wait 和 notify 前提是 加锁
  2. 存放 put() ,满就等待 wait,进入阻塞,等待取出,notify唤醒
  3. 取出 take(),空就等待 wait,进入阻塞,等待存放,notify唤醒
//put  阻塞添加
    synchronized public void put(int value) throws InterruptedException {
        //判满
        if(useSize == elem.length){
            //return;
            //等待取出
            this.wait();
        }
        //添加  放到尾坐标上
        elem[tail] = value;
        tail++;
        //放到最后 从头开始
        if(tail == elem.length){
            tail = 0;
        }
        useSize++;
        //唤醒获取等待
        this.notify();
    }
    //take 阻塞移除
    synchronized public int put() throws InterruptedException {
        //判空
        if(useSize == 0){
            //return -1;
            //等待存放
            this.wait();
        }
        //从头 取出
        int value = elem[head];
        head++;
        //取到尾 从头开始
        if(head == elem.length){
            head = 0;
        }
        useSize--;
        //唤醒存放等待
        this.notify();
        return value;
    }
  1. 上述加上 wait() 和 notify() 虽然可以进入等待 和 唤醒,但是还有一个问题,如果不是norify()唤醒的,其他代码中 interrupt 把wait唤醒了,条件不满足但是继续执行了,那么就会出现错误
  2. 写入操作可能出重排序排序

解决

  1. 提前唤醒,那就在判断一次条件是否满足,将 if 给为 while
  2. 给元素加上 volatile
class MyBlockingQueue{
    //数组  存储数据
    private  int[] elem = new int[1000];
    //头尾
    volatile private int head = 0;
    volatile private int tail = 0;
    //有效数据
    volatile private int useSize = 0;

    //put  阻塞添加
    synchronized public void put(int value) throws InterruptedException {
        //判满
        while(useSize == elem.length){
            //return;
            //等待取出
            this.wait();
        }
        //添加  放到尾坐标上
        elem[tail] = value;
        tail++;
        //放到最后 从头开始
        if(tail == elem.length){
            tail = 0;
        }
        useSize++;
        //唤醒获取等待
        this.notify();
    }
    //take 阻塞移除
    synchronized public Integer take() throws InterruptedException {
        //判空
        while(useSize == 0){
            //return -1;
            //等待存放
            this.wait();
        }
        //从头 取出
        int value = elem[head];
        head++;
        //取到尾 从头开始
        if(head == elem.length){
            head = 0;
        }
        useSize--;
        //唤醒存放等待
        this.notify();
        return value;
    }
}
public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue();
        // 消费者
        Thread t1 = new Thread(() -> {
            while (true) {
                try {
                    int value = queue.take();
                    System.out.println("消费: " + value);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 生产者
        Thread t2 = new Thread(() -> {
            int value = 0;
            while (true) {
                try {
                    System.out.println("生产: " + value);
                    queue.put(value);
                    value++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t2.start();
 }

java jstack分析线程阻塞_阻塞队列_09