目录
1 阻塞队列是什么
阻塞队列是⼀种特殊的队列. 也遵守 "先进先出" 的原则.
阻塞队列是⼀种线程安全的数据结构, 并且具有以下特性:
• 当队列满的时候, 继续⼊队列就会阻塞, 直到有其他线程从队列中取⾛元素.
• 当队列空的时候, 继续出队列也会阻塞, 直到有其他线程往队列中插⼊元素.
阻塞队列的⼀个典型应⽤场景就是 "⽣产者消费者模型". 这是⼀种⾮常典型的开发模型.
2 ⽣产者消费者模型
⽣产者消费者模式就是通过⼀个容器来解决⽣产者和消费者的强耦合问题。 ⽣产者和消费者彼此之间不直接通讯,⽽通过阻塞队列来进⾏通讯,所以⽣产者⽣产完数据之后不⽤等待消费者处理,直接扔给阻塞队列,消费者不找⽣产者要数据,⽽是直接从阻塞队列⾥取.
1. 阻塞队列能使⽣产者和消费者之间 解耦和
2. 阻塞队列就相当于⼀个缓冲区,平衡了⽣产者和消费者的处理能⼒. (削峰填⾕)
举一个生活中的例子
3 标准库中的阻塞队列
在 Java 标准库中内置了阻塞队列. 如果我们需要在⼀些程序中使⽤阻塞队列, 直接使⽤标准库中的即可.
• BlockingQueue 是⼀个接⼝. 真正实现的类是
• put ⽅法⽤于阻塞式的⼊队列, take ⽤于阻塞式的出队列.
• BlockingQueue 也有 offer, poll, peek 等⽅法, 但是这些⽅法不带有阻塞特性.
生产者消费者模型
//生产者消费者模型
public class Demo19 {
public static void main(String[] args) {
// 先搞个交易场所
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
// 负责生产元素
Thread t1 = new Thread(() -> {
int count = 0;
while (true) {
try {
queue.put(count);
System.out.println("生产元素: " + count);
count++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 负责消费元素
Thread t2 = new Thread(() -> {
while (true) {
try {
Integer n = queue.take();
System.out.println("消费元素: " + n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}
接下来自己实现一个阻塞队列, 此处就基于数组, 循环队列来模拟实现阻塞队列.
class MyBlockingQueue {
// 使用一个 String 类型的数组来保存元素. 假设这里只存 String.
private String[] items = new String[1000];
// 指向队列的头部
private int head = 0;
// 指向队列的尾部的下一个元素. 总的来说, 队列中有效元素的范围 [head, tail)
// 当 head 和 tail 相等(重合), 相当于空的队列.
private int tail = 0;
// 使用 size 来表示元素个数.
private int size = 0;
//入队列
public void put(String elem) {
if (size >= items.length) {
return;
}
items[tail] = elem;
tail++;
if (tail >= items.length) {
tail = 0;
}
size++;
}
//出队列
public String take() {
if (size == 0) {
return null;
}
String elem = items[head];
head++;
if (head >= items.length) {
head = 0;
}
size--;
return elem;
}
}
这是一个普通的队列, 现在要把它变成一个阻塞队列.
我们首先要考虑的是
1) 线程安全问题
由于put和take方法中涉及到了变量的修改, 所以这在多线程中这很可能会导致线程不安全问题. 所以先要给put和take方法加锁.
除了加锁以外, 还需要考虑内存可见性问题.
2) 该怎么实现阻塞
wait要和while搭配使用, 官方文档也是这样说的.
所以最后调整后的代码为
class MyBlockingQueue {
// 使用一个 String 类型的数组来保存元素. 假设这里只存 String.
private String[] items = new String[1000];
// 指向队列的头部
volatile private int head = 0;
// 指向队列的尾部的下一个元素. 总的来说, 队列中有效元素的范围 [head, tail)
// 当 head 和 tail 相等(重合), 相当于空的队列.
volatile private int tail = 0;
// 使用 size 来表示元素个数.
volatile private int size = 0;
private Object locker = new Object();
//入队列
public void put(String elem) throws InterruptedException {
synchronized (locker) {
while (size >= items.length) {
// 队列满了 暂时不能入队列
//return;
locker.wait();
}
items[tail] = elem;
tail++;
if (tail >= items.length) {
tail = 0;
}
size++;
locker.notify();
}
}
//出队列
public String take() throws InterruptedException {
synchronized (locker) {
while (size == 0) {
// 队列空了 暂时不能出队列
// return null;
locker.wait();
}
String elem = items[head];
head++;
if (head >= items.length) {
head = 0;
}
size--;
locker.notify();
return elem;
}
}
}
public class Demo20 {
public static void main(String[] args) {
// 交易场所
MyBlockingQueue queue = new MyBlockingQueue();
// 创建两个线程 表示生产者和消费者
Thread t1 = new Thread(() -> {
int count = 0;
while (true) {
try {
queue.put(count + "");
System.out.println("生产元素: " + count);
count++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
while (true) {
try {
String ret = queue.take();
Thread.sleep(1000);
System.out.println("消费元素: " + ret);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();;
t2.start();
}
}