目录
一、堵塞队列是啥
看名字就知道:
堵塞队列 是一个 队列。这个队列又带有堵塞的功能。
所以 堵塞队列 秉承着 先进先出 的原则。它是一种线程安全的数据结构
- 队列为满的时候,放元素会堵塞等待,直到队列有位置可以放
- 队列为空的时候,取元素会堵塞等待,直到队列有元素可以取
二、咋用堵塞队列
Java标准库中内置了堵塞队列,所以我们可以直接使用堵塞队列。
- BlockingQueue是一个接口,我们实际new的是LinkedBlockingQueue这个类。
- put ⽅法⽤于阻塞式的⼊队列, take ⽤于阻塞式的出队列。
- BlockingQueue 也有 offer, poll, peek 等⽅法, 但是这些⽅法不带有阻塞特性.
BlockingQueue<String> blockingQueue=new LinkedBlockingQueue<>(10);
blockingQueue.put("a");
String s=blockingQueue.take();
三、咋实现堵塞队列
这个堵塞队列结构(循环队列):
- 构造方法,就是 设置这个队列的长度
- 取队首元素take,和总体的思路队列的差不多,取之前看看有没有元素,取了之后head++,size--。但是堵塞队列需要锁和wait和notify,没有元素就wait(只要有元素就不会堵塞,所以在放进去元素之后notify就行了)
- 放队首元素put,也是和总体的思路,和队列差不多,放之前看看有没有满,放了之后tail++,size++。但是堵塞队列需要锁和wait和notify,元素放满了就wait(只要没放满就不会堵塞,所以在取元素之后notify就行了)
class MyBlockingQueue {
private int head = 0;
private int tail = 0;
private int size = 0;
public String[] data = null;
public MyBlockingQueue(int cap) {
this.data = new String[cap];
}
Object locker = new Object();
public void put(String s) throws InterruptedException {
synchronized (locker) {
if (size == data.length) {
this.wait();
}
data[tail] = s;
tail++;
if (tail >= data.length) {
tail = 0;
}
size++;
this.notify();
}
}
public String take() throws InterruptedException {
synchronized (locker) {
if (size == 0) {
this.wait();
}
String ret = data[head];
head++;
if (head >= data.length) {
head = 0;
}
size--;
this.notify();
return ret;
}
}
}
四、生产者消费者模型是啥
就是利用 堵塞队列 搭建出的一个模型。
就像我们的工厂流水线,生产者就是第一个部门的工人,他们在制作商品放到传送带(堵塞队列),然后给下一个部门的工人(消费者)。
如果上个部门生产的慢,放在传送带的也慢,这样下一个部门的工人没得拿就是堵塞了;
在如果上一个部门的工人生产的很快,传送带都放慢了不够放了,那么这个部门的人就可以等传送带的被下一个部门的工人拿完才就可以继续放。
⽣产者和消费者彼此之间不直接通讯,⽽通过阻塞队列来进⾏通讯,所以⽣产者⽣产完数据之后不⽤ 等待消费者处理,直接扔给阻塞队列,消费者不找⽣产者要数据,⽽是直接从阻塞队列⾥取。
五、咋实现生产者消费者模型
结构如下:
- 需要一个堵塞队列,也就是new一个LinkedBlockingQueue
- 需要一个生产者线程,这样就可以模拟出生产者一直在生产,我们这里用Random随机数来作为传输的元素,每生产一个休息一秒,put进去堵塞队列
- 需要一个消费者线程,这样就可以模拟出消费者一直在消费,我们这里直接take这个堵塞队列就行了。
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();
}
六、生产者消费者模型的好处
- 阻塞队列就相当于⼀个缓冲区,平衡了⽣产者和消费者的处理能⼒. (削峰填⾕)
⽐如在 "秒杀" 场景下,服务器同⼀时刻可能会收到⼤量的⽀付请求。
如果直接处理这些⽀付请求,服务器可能扛不住(每个⽀付请求的处理都需要⽐较复杂的流程)。 这个时候就可以把这些请求都放到⼀个 阻塞队列中, 然后再由消费者线程慢慢的来处理每个⽀付请求。这样做可以有效进⾏ "削峰",防⽌服务器被突然到来的⼀波请求直接冲垮。
- 阻塞队列也能使⽣产者和消费者之间 解耦.
⽐如过年⼀家⼈⼀起包饺⼦。⼀般都是有明确分⼯,⽐如⼀个⼈负责擀饺⼦⽪, 其他⼈负责包。擀饺⼦⽪的⼈就是 "⽣产者", 包饺⼦的⼈就是 "消费者". 擀饺⼦⽪的⼈不关⼼包饺⼦的⼈是谁(能包就⾏, ⽆论是⼿⼯包, 借助⼯具, 还是机器包), 包饺⼦的⼈也不关⼼擀饺⼦⽪的⼈是谁(有饺⼦⽪就⾏, ⽆论是⽤擀⾯杖擀的, 还是拿罐头瓶擀, 还是直接从超市买的).