一、阻塞队列是什么?
1.阻塞队列(BlockingQueue) 是一种 线程安全的队列 ,当队列为空或满时,线程会 自动阻塞等待。
2.阻塞特性
a)队列为空,尝试出队列,出队列操作就会阻塞.阻塞到其他线程添加元素为止.
b)队列为满,尝试入队列,入队列操作也会阻塞.阻塞到其他线程取走元素为止.
阻塞队列,一个最主要的应用场景,就是实现"生产者消费者模型"多线程编程中,一种典型的编码技巧.
经典例子:厨房后厨
场景
厨师👨🍳负责做菜(生产者)
服务员💁负责端菜(消费者)
出餐台 = 阻塞队列
情况一: 出餐台满了
厨师做好的菜没地方放 只能等服务员端走
厨师被"阻塞"
情况二:出餐台空了
服务员没菜可端 只能等厨师做
服务员被"阻塞"
二、生产者消费者模型的两个重要优势
1.解耦合(不一定是两个线程之间,也可以是两个服务器之间)

本来是A和B耦合 现在成了A和队列耦合,B和队列耦合???
A的代码中就看不见B了 B的代码中也看不见A了.
A的代码中和B的代码中只能看到队列
降低耦合,是为了让后续修改的时候成本低,队列一般不会修改
之前AB耦合带来的问题:担心A的代码需要修改的时候,B也得同时改 反之亦然
2.削峰填谷

一般来说A这种上游的服务器,尤其是入口的服务器,干的活更简单,单个请求消耗的资源数少像B这种下游的服务器,通常承担更重的任务量,复杂的计算/存储工作,单个请求消耗的资源数更多.日常工作中,确实是会给B这样角色的服务器分配更好的机器.即使如此,也很难保证B承担的访问量能够比A更高.

趁着峰值过去了,B仍然继续消费数据.利用波谷的时间,来赶紧消费之前积压的数据
生产者消费者模型付出的代价
1)引入队列之后,整体的结构会更复杂.此时,就需要更多的机器,进行部署.生产环境的结构会更复杂.管理起来更麻烦.
2)效率会有影响
三、阻塞队列使用
Java标准库中,提供了现成的阻塞队列.BlockingQueue
java
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("aaa");
String elem = queue.take();
System.out.println(elem);
}

当队列无东西可拿时则会进入阻塞等待

可以设置阻塞队列容量大小

如果不设置capacity,默认是一个非常大的数值实际开发,一般建议大家能够设置上要求的最大值.否则队列可能变的非常大,导致把内存耗尽,产生 内存超出范围这样的异常....
java
public static void main(String[] args) {
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(1000);
Thread producer = new Thread(() -> {
int n = 0;
while (true){
try {
queue.put(n);
System.out.println("生产元素" + n);
n++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"producer");
Thread consumer = new Thread(() -> {
while (true){
try {
Integer n = queue.take();
System.out.println("消费元素 " + n);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"consumer");
producer.start();
consumer.start();
}

此处代码设置了容量大小为1000 而运行结果会发现生产元素和消费元素已经是跑到了百万去???
阻塞队列,没有提供一个"阻塞的获取队首元素的操作"直接运行,生产者和消费者两个线程的速度,旗鼓相当,所以很难见到阻塞效果。
此时就需要在生产者或消费者中加入sleep


四、模拟实现
java
class MyBlockingQueue {
//用数组作为底层存储结构
private String[] data = null;
//队首 指向下一个要取出的元素位置
private int head = 0;
//队尾 指向下一个要插入元素的位置
private int tail = 0;
//元素个数
private int size = 0;
public MyBlockingQueue(int capacity) {
data = new String[capacity];
}
// 生产者调用的方法:向队列中放入元素
public void put(String elem) throws InterruptedException {
// 对当前对象加锁,保证线程安全
synchronized (this) {
// 使用 while 是为了防止线程被错误唤醒
while (size >= data.length){
this.wait();
}
//在队尾放入元素
data[tail] = elem;
tail++;
// 如果 tail 到达数组末尾,则回到 0(循环队列)
if(tail >= data.length){
tail = 0;
}
size++;
this.notify();
}
}
public String take() throws InterruptedException {
synchronized (this){
while (size == 0){
this.wait();
}
String ret = data[head];
head++;
if(head >= data.length){
head = 0;
}
size--;
this.notify();
return ret;
}
}
}
public class Demo {
public static void main(String[] args) {
MyBlockingQueue queue = new MyBlockingQueue(1000);
Thread producer = new Thread(() ->{
int n = 0;
while (true){
try {
queue.put(n + "");
System.out.println("生产元素" + n);
n++;
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"producer");
Thread consumer = new Thread(() ->{
while (true){
String n = null;
try {
n = queue.take();
System.out.println("消费元素" + n);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"consumer");
producer.start();
consumer.start();
}
}
