1. 背景
在 [[Java数据结构11-死磕Java队列-LinkedList]] 文章中,我们看到 LinkedList
在获取元素或者插入元素过程中,因为队列是非阻塞,所以不对对应用进行阻断,这在很多时候,并不能满足我们的实际要求。当然这种是否需要阻塞也是看实际情况而言,就其效率和实际应用设计程度相对有界非阻塞而言稍微复杂,因阻塞而挂起的场景需要额外做适配,防止线程长时间被挂起而达不到释放,造成资源过渡开销。
2. 适用场景
当我们在生产或者消费队列中的元素,如果我们获取元素过程中队列无元素或者写入队列时候,队列的容量已经满了,此时不论是消费者线程还是生产者线程都应该被阻塞。直到队列中有元素或者队列容量非最大。
3. 实例
有界阻塞队列常用的主要有 ArrayBlockingQueue
和 LinkedBlockingQueue
,他们实现了 BlockingQueue 阻塞队列接口,并在基础上位置一个有容量上限的集合。
3.1. ArrayBlockingQueue
ArrayBlockingQueue
在内部维持一个 Object[]
类型的数组 。
3.1.1. 构造函数
- ArrayBlockingQueue(int capacity):指定容量上限,并且默认的访问顺序
- ArrayBlockingQueue(int capacity, boolean fair):指定容量上限,并且可指定所需要访问是否按照顺序
- ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c):指定容量上限,并且可指定所需要访问是否按照顺序,以及指定元素的集合
3.1.2. 常用方法
方法名 | 描述 |
---|---|
add | 将指定的元素添加为队列尾部,如果队列已满,则抛出异常 |
offer | 将指定的元素添加为队列尾部,如果队列已满,则返回 False |
put | 将指定的元素插入队列尾部,如果队列已满,则等待空间的出现。 |
size | 返回队列当前的容量大小 |
remove | 从这个队列中删除一个指定元素的单个实例 |
poll | 检索并删除此列表的头(第一个元素) |
pop | 从此列表表示的堆栈中弹出一个元素,换句话说,删除并返回此列表的第一个元素 |
peek | 检索但不删除此列表的头(第一个元素) |
take | 检索并删除此列表的头(第一个元素),没有元素的过程中,线程会等待 |
3.1.3. 样例
队列容量大小为 10 ,三个生产者线程和三个消费者线程,生产者线程每次朝队列中添加 5 个元素,消费者线程每次从队列头部消费 5 个元素。
在生产者添加过程中,如果队列已满,则生产者队列阻塞等待,最后,消费者线程如果遇到队列中没有元素,那么消费者线程将一直阻塞等待。
- ArrayBlockingQueueDemo:数组队列核心实例方法
public class ArrayBlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
BlockingQueueUtil util = new BlockingQueueUtil(queue);
ExecutorService exe= ThreadPoolsUtil.doCreate(2,10,"BLOCKING");
for (int i = 0; i < 3; i++) {
exe.submit(new ProducerCase(util));
}
for (int i = 0; i < 3; i++) {
exe.submit(new ConsumberCase(util));
}
exe.shutdown();
}
}
- ConsumberCase:消费者
class ConsumberCase implements Runnable {
private BlockingQueueUtil util;
ConsumberCase(BlockingQueueUtil util) {
this.util = util;
}
@SneakyThrows
@Override
public void run() {
for (int i = 0; i < 5; i++) {
util.romove();
}
}
}
- ProducerCase:生产者
class ProducerCase implements Runnable {
private BlockingQueueUtil util;
ProducerCase(BlockingQueueUtil util) {
this.util = util;
}
@SneakyThrows
@Override
public void run() {
for (int i = 0; i < 5; i++) {
String val = "Qu_" + i;
if (!util.add(val)) {
System.out.println(Thread.currentThread().getName() + "; [Value ]: " + val + " ;队列已满,需要等待");
}
}
}
}
- BlockingQueueUtil:队列工具类
public class BlockingQueueUtil {
BlockingQueue<String> queue;
BlockingQueueUtil(BlockingQueue<String> queue) {
this.queue = queue;
}
public boolean add(String val) {
boolean flag = queue.offer(val);
System.out.println(Thread.currentThread().getName() +"; [Value ]: "+ val+ " ;添加后队列容量大小为:" + queue.size());
return flag;
}
/**
* 没有元素,线程则等待
*
* @param
* @return String
* @author <a href="https://github.com/rothschil">Sam</a>
**/
public String romove() throws InterruptedException {
String val = queue.take();
System.out.println(Thread.currentThread().getName() +"; [Value ]: "+ val+" ;剩余容量大小为:" + queue.size());
return val;
}
}
3.1.4. 日志
BLOCKING-1-thread-2; [Value ]: Qu_0 ;添加后队列容量大小为:2
BLOCKING-1-thread-2; [Value ]: Qu_1 ;添加后队列容量大小为:3
BLOCKING-1-thread-1; [Value ]: Qu_0 ;添加后队列容量大小为:2
BLOCKING-1-thread-2; [Value ]: Qu_2 ;添加后队列容量大小为:4
BLOCKING-1-thread-1; [Value ]: Qu_1 ;添加后队列容量大小为:5
BLOCKING-1-thread-2; [Value ]: Qu_3 ;添加后队列容量大小为:6
BLOCKING-1-thread-1; [Value ]: Qu_2 ;添加后队列容量大小为:7
BLOCKING-1-thread-2; [Value ]: Qu_4 ;添加后队列容量大小为:8
BLOCKING-1-thread-1; [Value ]: Qu_3 ;添加后队列容量大小为:9
BLOCKING-1-thread-1; [Value ]: Qu_4 ;添加后队列容量大小为:10
BLOCKING-1-thread-2; [Value ]: Qu_0 ;添加后队列容量大小为:10
BLOCKING-1-thread-1; [Value ]: Qu_0 ;剩余容量大小为:9
BLOCKING-1-thread-2; [Value ]: Qu_0 ;队列已满,需要等待
BLOCKING-1-thread-1; [Value ]: Qu_0 ;剩余容量大小为:8
BLOCKING-1-thread-2; [Value ]: Qu_1 ;添加后队列容量大小为:9
BLOCKING-1-thread-1; [Value ]: Qu_1 ;剩余容量大小为:8
BLOCKING-1-thread-2; [Value ]: Qu_2 ;添加后队列容量大小为:9
BLOCKING-1-thread-2; [Value ]: Qu_3 ;添加后队列容量大小为:9
BLOCKING-1-thread-1; [Value ]: Qu_2 ;剩余容量大小为:8
BLOCKING-1-thread-2; [Value ]: Qu_4 ;添加后队列容量大小为:10
BLOCKING-1-thread-1; [Value ]: Qu_1 ;剩余容量大小为:9
BLOCKING-1-thread-2; [Value ]: Qu_3 ;剩余容量大小为:8
BLOCKING-1-thread-1; [Value ]: Qu_2 ;剩余容量大小为:7
BLOCKING-1-thread-2; [Value ]: Qu_4 ;剩余容量大小为:6
BLOCKING-1-thread-1; [Value ]: Qu_3 ;剩余容量大小为:5
BLOCKING-1-thread-2; [Value ]: Qu_4 ;剩余容量大小为:4
BLOCKING-1-thread-1; [Value ]: Qu_1 ;剩余容量大小为:3
BLOCKING-1-thread-2; [Value ]: Qu_2 ;剩余容量大小为:2
BLOCKING-1-thread-1; [Value ]: Qu_3 ;剩余容量大小为:1
BLOCKING-1-thread-2; [Value ]: Qu_4 ;剩余容量大小为:0
3.2. LinkedBlockingQueue
LinkedBlockingQueue
在内部维持一个链表。
3.2.1. 构造函数
- LinkedBlockingQueue():这是创造一个无界的队列,容量上限就是
Integer.MAX_VALUE
<不推荐使用> - LinkedBlockingQueue(int capacity):创建一个有容量上限的队列
- LinkedBlockingQueue(Collection<? extends E> c):根据给定的集合,创建队列
3.2.2. 常用方法
方法名 | 描述 |
---|---|
offer | 将指定的元素添加为队列尾部,如果队列已满,则返回 False |
put | 将指定的元素插入队列尾部,如果队列已满,则等待空间的出现。 |
size | 返回队列当前的容量大小 |
remove | 从这个队列中删除一个指定元素的单个实例 |
poll | 检索并删除此列表的头(第一个元素) |
peek | 检索但不删除此列表的头(第一个元素) |
take | 检索并删除此列表的头(第一个元素),没有元素的过程中,线程会等待 |
iterator | 以适当的顺序返回这个队列中的元素的迭代器 |
dequeue | 将一个节点从队列的头部移除 |
3.2.3. 样例
public class LinkedBlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> queue = new LinkedBlockingQueue<>(10);
BlockingQueueUtil util = new BlockingQueueUtil(queue);
ExecutorService exe= ThreadPoolsUtil.doCreate(2,10,"BLOCKING");
for (int i = 0; i < 3; i++) {
exe.submit(new ProducerCase(util));
}
for (int i = 0; i < 3; i++) {
exe.submit(new ConsumberCase(util));
}
exe.shutdown();
}
}
3.2.4. 日志
BLOCKING-1-thread-1; [Value ]: Qu_0 ;添加后队列容量大小为:1
BLOCKING-1-thread-2; [Value ]: Qu_0 ;添加后队列容量大小为:2
BLOCKING-1-thread-1; [Value ]: Qu_1 ;添加后队列容量大小为:3
BLOCKING-1-thread-2; [Value ]: Qu_1 ;添加后队列容量大小为:4
BLOCKING-1-thread-2; [Value ]: Qu_2 ;添加后队列容量大小为:6
BLOCKING-1-thread-1; [Value ]: Qu_2 ;添加后队列容量大小为:5
BLOCKING-1-thread-2; [Value ]: Qu_3 ;添加后队列容量大小为:7
BLOCKING-1-thread-2; [Value ]: Qu_4 ;添加后队列容量大小为:9
BLOCKING-1-thread-1; [Value ]: Qu_3 ;添加后队列容量大小为:8
BLOCKING-1-thread-1; [Value ]: Qu_4 ;添加后队列容量大小为:10
BLOCKING-1-thread-1; [Value ]: Qu_0 ;添加后队列容量大小为:10
BLOCKING-1-thread-1; [Value ]: Qu_0 ;队列已满,需要等待
BLOCKING-1-thread-1; [Value ]: Qu_1 ;添加后队列容量大小为:10
BLOCKING-1-thread-1; [Value ]: Qu_1 ;队列已满,需要等待
BLOCKING-1-thread-1; [Value ]: Qu_2 ;添加后队列容量大小为:10
BLOCKING-1-thread-1; [Value ]: Qu_3 ;添加后队列容量大小为:10
BLOCKING-1-thread-1; [Value ]: Qu_3 ;队列已满,需要等待
BLOCKING-1-thread-1; [Value ]: Qu_4 ;添加后队列容量大小为:10
BLOCKING-1-thread-2; [Value ]: Qu_0 ;剩余容量大小为:9
BLOCKING-1-thread-1; [Value ]: Qu_4 ;队列已满,需要等待
BLOCKING-1-thread-1; [Value ]: Qu_1 ;剩余容量大小为:8
BLOCKING-1-thread-2; [Value ]: Qu_0 ;剩余容量大小为:9
BLOCKING-1-thread-1; [Value ]: Qu_1 ;剩余容量大小为:7
BLOCKING-1-thread-2; [Value ]: Qu_2 ;剩余容量大小为:6
BLOCKING-1-thread-2; [Value ]: Qu_2 ;剩余容量大小为:5
BLOCKING-1-thread-1; [Value ]: Qu_3 ;剩余容量大小为:4
BLOCKING-1-thread-2; [Value ]: Qu_3 ;剩余容量大小为:3
BLOCKING-1-thread-1; [Value ]: Qu_4 ;剩余容量大小为:2
BLOCKING-1-thread-2; [Value ]: Qu_4 ;剩余容量大小为:1
BLOCKING-1-thread-1; [Value ]: Qu_2 ;剩余容量大小为:0