前言
什么是阻塞队列?
-
阻塞队列是一种特殊类型的队列,它在插入或删除元素时会发生状态阻塞。当队列满时,尝试插入新元素的线程将等待直到队列中有空间可用。同样地,当队列为空时,尝试删除元素的线程将等待,直到队列中有可用元素。
-
也就是说明,它是一个队列,具有先入先出的性质,但是不同于普通的队列,队列中若没有元素,此时如果出队列,也不会报错,而是会一直阻塞等待入队列执行,然后才出队列。
-
阻塞队列是多线程编程中非常重要的一种数据结构,它可以很好地协调不同线程之间的工作,防止数据竞争和资源浪费。Java中的BlockingQueue就是一种典型的阻塞队列实现,常用于线程池、消息队列、生产者消费者模型等场景。
提示:以下是本篇文章正文内容,下面案例可供参考
一、BlockingQueue
BlockingQueue是java.util.concurrent提供的一种线程安全的阻塞队列的接口。(接口不能被实例化)
BlockingQueue提供了多个实现类,包括:
-
ArrayBlockingQueue:基于数组的有界(必须设定容量大小)阻塞队列,按照先进先出的原则来进行元素排列。
-
LinkedBlockingQueue:基于链表的无界(默认大小为Integer.MAX_VALUE)阻塞队列,按照先进先出的原则来进行元素排列。如果构造方法不指定大小,则默认大小为Integer.MAX_VALUE。
-
SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须被另一个线程的取出操作所配对,并且插入操作会被阻塞直到有另一个线程执行相应的取出操作。
-
PriorityBlockingQueue:基于堆的无界阻塞队列,可以根据元素的比较函数自动进行优先级排序。
BlockingQueue 的主要方法包括:put(E e)、take()、offer(E e)、poll()等。
-
put()和take()方法会自动阻塞线程,直到队列中有空间或元素;
-
offer()和poll()方法则会返回一个特殊值,以标识成功或失败,并不会阻塞线程。(就相当于一个普通的队列)
使用BlockingQueue可以很方便地实现线程间通信和协作,常用于生产者消费者模式和线程池等场景。
二、使用步骤
基本性质
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
BlockingDeque<String> blockingDeque = new LinkedBlockingDeque<>();//阻塞队列
//阻塞队列核心方法
//1. put 入队列
blockingDeque.put("hello");
blockingDeque.put("hello");
blockingDeque.put("hello");
blockingDeque.put("hello");
blockingDeque.put("hello");
//blockingDeque.put(null);//错误,阻塞队列不能插入 null,
// Deque双端队列,可以插入 null,官方文档也不建议这样做
// 因为 null被用作特殊的返回值通过各种方法来表示 deque是空的。
//2. take 出队列
String result = blockingDeque.take();
System.out.println(result);
String result1 = blockingDeque.take();
System.out.println(result1);
String result2 = blockingDeque.take();
System.out.println(result2);
String result3 = blockingDeque.take();
System.out.println(result3);
String result4 = blockingDeque.take();
System.out.println(result4);
String result5 = blockingDeque.take();
//由于只有5个元素,所以第六个一直阻塞等待
System.out.println(result5);
}
}
执行结果
生产者消费者模型
public class ThreadDemo {
public static void main(String[] args) {
BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>();
//消费者 t1
Thread t1 = new Thread(() -> {
// 不断取出 t2生产出的 value
while (true) {
try {
int value = blockingDeque.take();
System.out.println("消费元素:" + value);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
//生产者 t2
Thread t2 = new Thread(() -> {
int value = 0;
while (true) {
// 不断生产 value
try {
System.out.println("生产元素:" + value);
blockingDeque.put(value);
value++;
// 防止生产的过快,不便于观察
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
}
}
输出结果
运行可以看出,每次消费元素后,都需要等待生产者生产元素
三、模拟实现阻塞队列
通过使用 volatile 和 synchronized
class MyBlockingQueue {
private int[] items = new int[1000];
// [head, tail)为有效元素,左闭右开
// volatile保证原子性,避免指令重排序
volatile private int head;
volatile private int tail;
volatile private int size;
//入队列
synchronized public void put(int elem) throws InterruptedException {
//不使用 if,防止被其他线程的 interrupted唤醒,此时还没有满足条件
while (size == items.length) {
this.wait();//队列满,阻塞
}
items[tail] = elem;
tail++;
//tail到队尾需要从头再来
if (tail == items.length) {//该写法更快
tail = 0;
}
// 该写法能达到和上面 if一样的效果
//tail %= items.length;
size++;
this.notify();//唤醒take
}
//出队列
synchronized public Integer take() throws InterruptedException {
// 同理
while (size == 0) {
this.wait();//空队列,阻塞
}
int value = items[head];
head++;
if (head == items.length) {
head = 0;
}
size--;
this.notify();//唤醒put
return value;
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyBlockingQueue queue = new MyBlockingQueue();
Thread t1 = new Thread(() -> {
while (true) {
try {
int value = queue.take();
System.out.println("消费元素:" + value);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//生产者
Thread t2 = new Thread(() -> {
int value = 0;
while (true) {
try {
System.out.println("生产元素:" + value);
queue.put(value);
value++;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}
输出结果
和之前调用接口的功能相同。