目录

1.阻塞式队列

2.生产者—消费者模型


1.阻塞式队列

1.1 概念

这里我们提到的阻塞式队列并非我们之前所理解的那种用于存放阻塞线程PCB的链表,而是一种数据结构。那我们在了解阻塞式队列概念的同时,也顺便了解一下其他特殊的队列吧。

1.优先级队列:出队时按照一定的优先级顺序,优先级高的先出

2.阻塞队列:保证线程安全。如果队列为空时尝试出队,就会阻塞;如果队列为满时尝试入队也会进入阻塞状态。

3.无锁队列:线程安全的队列。内部没有使用锁,更加高效,但占用更多cpu资源

4.消息队列:在队列中涵盖多种不同类型的”元素“,取元素时可以按照某个类型来取,做到针对该类型的”先进先出“

当然这里我们只讲解第二种阻塞队列,其他感兴趣的可以自行上网了解。 

1.2 阻塞式队列的实现 

虽然java中已经为我们提供了阻塞队列,但是我们还是自己手动实现一下会有更深刻的印象。

其中可能涉及一些队列的基础知识,有不了解的同学可以先看这篇文章复习一下。


// 基于数组的方式来实现队列.
// 循环队列
// 提供两个核心方法:
// 1. put 入队列
// 2. take 出队列
public class MyBlockingQueue {
    private int[] items=new int[1000];
    // 队首的位置
    private int head = 0;
    // 队尾的位置
    private int tail = 0;
    // 队列的元素个数
    volatile private int size = 0;

    //入队
    public void put(int val) throws InterruptedException{
        synchronized (this){//谁调用锁谁
            while(size==items.length){
                //队列已满,等待其他线程从队列中取走元素
                //采用while而不采用if的原因是多次判断防止出现意外
                this.wait();
            }
            items[tail]=val;
            tail++;
            // tail = tail % items.length;
            //不使用这种方法的原因是%运算的速度相对较慢,但也是正确的方法
            if (tail == items.length) {
                // 注意 如果 tail 达到数组末尾, 就需要从头开始
                //因为实现的是循环队列
                tail = 0;
            }
            size++;//存放数增加
            this.notify();
            //此时队列一定不为空,唤醒阻塞的take方法,即使没有线程在wait也无所谓
        }
    }

    // 出队
    public Integer take() throws InterruptedException{
        int ret=0;
        synchronized (this){
            while(size==0){
                this.wait();
            }
            ret=items[head];
            head++;
            if(head==items.length){
                head=0;
            }
            size--;
            this.notify();//互相唤醒
        }
        return ret;
    }
}

为了简化所以我们只是简单实现了put(入队)和take(出队)两个方法,主要是让大家理解阻塞式队列的特性。然后我们需要重点理解的是我们的put和take方法实际上是相互唤醒的

java怎么解除阻塞 java阻塞系数_阻塞队列

 

大家要理解这个关系,为什么能够相互唤醒?

如果队列为空,那么在插入成功后就不为空了,所以在put成功后put就能取唤醒take;

同理如果队列已满,那么出队后一定留有空位,所以take成功后也能够去唤醒put。 

2. 生产者—消费者模型 

2.1 概念

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。 生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等 待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取。可以看作是阻塞式队列的实际运用。再通俗一点我们可以把他看作一个供求关系,生产者负责供应物品给消费者。

2.2 作用

他的作用主要有两点:

1.阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力

java怎么解除阻塞 java阻塞系数_开发语言_02

 比如我们看这张图,A负责从网络上获取数据传输给B,网络上有大量的数据,但由于A不负责数据处理所以影响不大,但是B要负责数据处理,A不断将海量数据传输给B,而B负责数据处理,计算压力非常大。那么此时我们在A与B之间加上一个阻塞队列变成下图

java怎么解除阻塞 java阻塞系数_java_03

此时即使A依然在传输海量数据,但是多出来的压力由阻塞队列分担了,B就可以根据自己的频率来处理数据,这样就 平衡了生产者和消费者的处理能力。

2.阻塞队列也能使生产者和消费者之间解耦合


比如过年一家人一起包饺子. 一般都是有明确分工, 比如一个人负责擀饺子皮, 其他人负责包. 擀饺 子皮的人就是 "生产者", 包饺子的人就是 "消费者"

擀饺子皮的人不关心包饺子的人是谁(能包就行, 无论是手工包, 借助工具, 还是机器包), 包饺子的人 也不关心擀饺子皮的人是谁(有饺子皮就行, 无论是用擀面杖擀的, 还是拿罐头瓶擀, 还是直接从超 市买的)

降低耦合度的直接好处就是当一个程序的一部分出现问题不会影响其他正常的部分。

2.3 生产者—消费者模型简单代码实现 

我们需要用到阻塞队列这一数据结构,java中已经自带。

1.BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue.

2.put 方法用于阻塞式的入队列, take 用于阻塞式的出队列.

3.BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.

import java.util.*;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;


public class Demo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();
        Thread customer = new Thread(() -> {
            while (true) {
                try {
                    int value = blockingQueue.take();
                    System.out.println("消费元素: " + value);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者"); customer.start();
        Thread producer = new Thread(() -> {
            Random random = new Random();
            while (true) {
                try {
                    int num = random.nextInt(1000);
                    System.out.println("生产元素: " + num);
                    blockingQueue.put(num);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者");
        producer.start();
        customer.join();
        producer.join();
    }
}

 或者我们也可以用自己实现的阻塞队列来实现:

class MyBlockingQueue {
    private int[] items=new int[1000];
    // 队首的位置
    private int head = 0;
    // 队尾的位置
    private int tail = 0;
    // 队列的元素个数
    volatile private int size = 0;

    //入队
    public void put(int val) throws InterruptedException{
        synchronized (this){//谁调用锁谁
            while(size==items.length){
                //队列已满,等待其他线程从队列中取走元素
                //采用while而不采用if的原因是多次判断防止出现意外
                this.wait();
            }
            items[tail]=val;
            tail++;
            // tail = tail % items.length;
            //不使用这种方法的原因是%运算的速度相对较慢,但也是正确的方法
            if (tail == items.length) {
                // 注意 如果 tail 达到数组末尾, 就需要从头开始
                //因为实现的是循环队列
                tail = 0;
            }
            size++;//存放数增加
            this.notify();
            //此时队列一定不为空,唤醒阻塞的take方法,即使没有线程在wait也无所谓
        }
    }

    // 出队
    public Integer take() throws InterruptedException{
        int ret=0;
        synchronized (this){
            while(size==0){
                this.wait();
            }
            ret=items[head];
            head++;
            if(head==items.length){
                head=0;
            }
            size--;
            this.notify();//互相唤醒
        }
        return ret;
    }
}

public class MyBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        MyBlockingQueue queue = new MyBlockingQueue();
        Thread customer = new Thread(() -> {
            while (true) {
                int value = 0;
                try {
                    value = queue.take();
                    System.out.println("消费: " + value);

                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        customer.start();

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

这里我们想要的效果是生产者先生产部分数据,然后再由消费者慢慢消耗,大家可以自行去尝试运行结果。