1.阻塞队列
1.1 什么是阻塞队列
阻塞队列是一种特殊的队列数据结构,具备在队列为空或已满时,自动阻塞等待的特性。它是多线程编程中常用的同步工具,用于在多个线程之间安全地传递和共享数据。
具有以下特性:
- 当队列满的时候,继续入队列就会阻塞,直到有其他线程从队列中取走元素。
- 当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插入元素。
阻塞队列的一个典型应用场景就是 “生产者消费者模型” 。什么是 “生产者消费者模型” ?生产者消费者模型是一种并发编程模型,通过共享的缓冲区实现生产者线程生成数据并放入,消费者线程从中取出并处理的异步协作方式。
1.2 标准库中的阻塞队列
Java标准库中阻塞队列:java.util.concurrent.BlockingQueue
,这是一个接口,它里面提供的常用方法如下。
方法 | 描述 |
---|---|
add(E e) | 在队列尾部插入一个元素,如果队列已满,抛出IllegalStateException 异常 |
offer(E e) | 在队列尾部插入一个元素,如果队列已满,返回false |
put(E e) | 在队列尾部插入一个元素,如果队列已满,阻塞等待 |
remove() | 从队列头部移除并返回一个元素,如果队列为空,抛出NoSuchElementException 异常 |
poll() | 从队列头部移除并返回一个元素,如果队列为空,返回null |
take() | 从队列头部移除并返回一个元素,如果队列为空,阻塞等待 |
element() | 返回队列头部的元素,但不移除,如果队列为空,抛出NoSuchElementException 异常 |
peek() | 返回队列头部的元素,但不移除,如果队列为空,返回null |
实现该接口的类有很多,常用的有:
LinkedBlockingQueue
: 一个基于链表的阻塞队列。ArrayBlockingQueue
: 一个基于数组的有界阻塞队列。PriorityBlockingQueue
:一个支持优先级排序的阻塞队列。
使用标准库中的阻塞队列来实现生产者消费者模型:
public static void main(String[] args) {
//固定数量为 30,生产数量超过30后阻塞。
BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>(30);
//消费者
Thread consumer = new Thread(()->{
while(true){
try {
//开始消费
Integer result = blockingQueue.take();
System.out.println("消费元素:" + result);
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//生产者
Thread producer =new Thread(()->{
int count = 0;
while(true){
try {
//开始生产
blockingQueue.put(count);
System.out.println("生产元素:" + count);
count++;
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
consumer.start();
producer.start();
}
1.3 阻塞队列的简单模拟实现
这里只实现 put()
、take()
这个两个核心方法。
- 先实现一个非阻塞的队列,然后再来改进。
public class MyBlockingQueue {
//队头
private int head = 0;
//队尾
private int tail = 0;
//有效长度
private int useSize = 0;
private final int[] items;
public MyBlockingQueue(){
items = new int[1000];
}
public MyBlockingQueue(int size){
items = new int[size];
}
//入队
public void put(int value){
if(useSize == items.length){
return;
}
items[tail++] = value;
//循环队列的性质 也可写成:tail = tail % items.length
if(tail >= items.length){
tail = 0;
}
useSize++;
}
//出队
public Integer take(){
if(useSize == 0){
return null;
}
Integer result = items[head++];
if(head >= items.length){
head = 0;
}
useSize--;
return result;
}
}
- 对
put()
与take()
加锁,对易变的变量加上volatile
来保证内存可见性。
public class MyBlockingQueue {
private volatile int head = 0;
private volatile int tail = 0;
private volatile int useSize = 0;
private final int[] items;
public MyBlockingQueue(){
items = new int[1000];
}
public MyBlockingQueue(int size){
items = new int[size];
}
public void put(int value) throws InterruptedException {
//对本对象加锁
synchronized (this){
while(useSize == items.length){
this.wait();
}
items[tail++] = value;
//循环队列的性质 也可写成:tail = tail % items.length
if(tail >= items.length){
tail = 0;
}
useSize++;
//唤醒take中的 wait()
this.notify();
}
}
public Integer take() throws InterruptedException {
int result = 0;
synchronized (this){
while(useSize == 0){
this.wait();
}
result = items[head++];
if(head >= items.length){
head = 0;
}
useSize--;
//唤醒 put 中的wait()
this.notify();
}
return result;
}
}
这里的wait()
为什么要套上一个while
循环呢?在wait()
的源码中有说明。