个人理解
总体认知
- 本质上是队列,但是并不一定是
FIFO
的,比如PriorityBlockingQueue
- 阻塞: 线程的状态
- 生产者阻塞: 队列满
- 消费者阻塞: 队列空
只要对阻塞队列
有一个整体的认知,相信理解其各种实现就很轻松了。
如果没有BlockingQueue
- 如果没有
BlockingQueue
,那多线程就需要在自己run
方法里实现各种场景的等待-通知机制 - 多线程将各种场景下的等待-通知机制解耦到了实现了
BlockingQueue
接口的各个工具类中,就像中间件MQ一样
阻塞队列主要的操作
插入到阻塞队列
插入元素 | 说明 |
---|---|
add(e) | 成功返回true,如果队列满了,则抛出IllegalStateException |
put(e) | 如果队列满了,则阻塞(当前线程后续会被唤醒),否则插入元素到队列中 |
offer(e) | 成功返回true,如果队列满了,返回false |
offer(e,time,unit) | 在设定的等待时间内,成功返回true; 超过等待时间,返回false |
从阻塞队列中移除
移除元素 | 说明 |
---|---|
remove(o) | 移除指定元素,如果存在返回true,否则返回false |
take() | 如果队列为空,则进入阻塞状态等待,否则队头元素出队。 JDK源码应用:ThreadPoolExecutor#getTask。 |
poll() | 队头元素出队,如果队列为空,则返回null |
poll(time,unit) | 如果队列为空,则等待获取,指定时间内如果队列仍然为空,返回null; 否则队头元素出队。 JDK源码应用:ThreadPoolExecutor#getTask。 |
将一个阻塞队列的一些元素移动到另一个Collection
drainTo(Collection<? super E> c,int maxElements)
;
将当前阻塞队列
中的元素出队指定个数到集合c中。
BlockingQueue的类型
无界队列
-
可以自动增长,无需设置size
-
LinkedBlockingDeque<Runnable> blockingDeque = new LinkedBlockingDeque<>();
上面代码中,其实底层已经设置了最大size为了
Integer。MAX_VALUE
,2^31-1,即二进制表示为31个1
-
-
由于是无界队列,它可能会变得非常大,所以使用无界队列因保证消费者快速地消费队列元素,否则可能会导致
OOM
,一般不推荐不设定阻塞队列的size
有界队列
-
有最大的size限制
-
LinkedBlockingDeque<String> blockingDeque = new LinkedBlockingDeque<>(16);
size为16,如果队列满了生产者会阻塞; 如果队列为空,消费者会阻塞。
-
-
应该多使用有界队列开发我们地程序,避免队列任务积压,资源耗尽
线程对阻塞队列操作的公平性
- 根据先来先执行的规则,如果线程被阻塞后,按照被阻塞的先后顺序被唤醒,则是公平的,反之为不公平
- 但是保证公平性会降低吞吐量,一般业务场景中不会要求同一类线程执行的先后顺序,可以使用非公平锁
- 例如
ArrayBlockingQueue
底层通过是否为公平锁的ReentrantLock
来实现是否公平,而ReentrantLock
使用双向链表保证公平性
Java中阻塞队列的一些实现类
ArrayBlockingQueue
- 基于数组实现的有界阻塞队列,初始化会分配指定size的Object数组
- 入队操作和出队操作都是使用的同一把锁
- 出队顺序是
FIFO
,在默认构造方法里不保证多线程操作的公平性
LinkedBlockingQueue
- 基于链表实现的有界(理论上size最大为2^31-1)或无界(不用用户手动指定size)阻塞队列,由于是基于链表实现的,所以初始化不需要为存储元素分配内存
- 入队操作和出队操作都是使用的两把不同的锁,唤醒也是两把锁对应的两个
Condition
对象- 例如生产者调用put方法时,如果队列没有满会尝试唤醒生产者,并且如果队列之前是空,入队之后还会唤醒消费者,该设计尽可能减少了线程等待的时间,适合高并发场景
- 出队顺序是
FIFO
Executors#newFixedThreadPool(int)
底层应用该队列
LinkedBlockingQueue与ArrayBlockingQueue异同
操作 | LinkedBlockingQueue | ArrayBlockingQueue |
---|---|---|
初始化 | 基于链表实现的初始化是可以不用指定队列大小(默认是Integer。MAX_VALUE); 由于是基于链表,初始化无需分配之后存储元素的内存 |
基于数组实现的,初始化时必须指定队列大小,初始化就要分配完整的Array内存 |
同步 | 入队和出队操作分别对应不同的锁,一共两把锁,唤醒自然也是两个不同的Condition对象; 高效地唤醒方式,尽可能减少生产者和消费者线程阻塞的时间,见 LinkedBlockingQueue#put |
入队和出队操作使用同一把锁, 粒度粗,锁未分离; 低效地唤醒方式(随机唤醒一个生产者或消费者) |
PriorityBlockingQueue
- 基于数据结构中的堆实现,所以底层用数组实现更简单,无界阻塞队列,就像
ArrayList
,即使设定容量,但需要入队更多的元素时,底层数组就会扩容 - 入队操作和出队操作都是使用的同一把锁
- 出队顺序默认是按照集合元素的
compare
方法的返回值,返回值小于0,越先被执行,构造方法也可以传入Comparator
的实现自定义比较的优先级
SynchronousQueue
- 不存储元素,生产者线程一次put必须要有一个消费者线程take,否则后续生产者线程会wait
Executors#newCachedThreadPool()
底层应用该队列
Demo代码
抽象设计打印和测试各个BlockingQueue的特点
-
用一个类
LogDemoProducerAndConsumer.java
, 目的是对测试各个BlockingQueue
提供一个公共的测试方法logDemoProducerAndConsumer
-
构造方法需要传入一个
BlockingQueue
的实例 -
logDemoProducerAndConsumer
将会通过日志的形式打印BlockingQueue
在一对一情况下的生产者-消费者的入队与出队操作,默认让消费者的消费速率小于生产者的生产速率 -
注意: 在本Demo中, 出队和打印log两步操作不具有原子性, 入队和打印log两步操作不具有原子性, 即log打印的顺序并不一定是多线程执行的顺序, 但通过log能体会到各个
BlockingQueue
的特性
/**
* 打印log: 生产者生产地元素和消费者消费地元素.<br>
* 注意: 在本Demo中, 出队和打印log两步操作不具有原子性, 入队和打印log两步操作不具有原子性<br>
* 即log打印的顺序并不一定是多线程执行的顺序, 但通过log能体会到各个BlockingQueue的特性
*
* @author www.cnblogs.com/theRhyme
* @date 2021/03/02
*/
@Slf4j
public class LogDemoProducerAndConsumer {
private int taskCount = 16;
private BlockingQueue<Integer> blockingQueue;
/** 生产者将元素入队, 注意入队和打印log两步操作不具有原子性, 即log打印的顺序并不一定是多线程执行的顺序 */
private final Runnable producerRunnable =
() -> {
for (int i = 0; i < taskCount; i++) {
try {
TimeUnit.MILLISECONDS.sleep(1);
this.put(i);
log.info("Producer ({}) put: ({}).", Thread.currentThread().getName(), i);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
};
/** 消费者将元素出队, 注意出队和打印log两步操作不具有原子性, 即log打印的顺序并不一定是多线程执行的顺序 */
private final Runnable consumerRunnable =
() -> {
try {
// 模拟消费者出队操作比生产者入队操作慢
TimeUnit.MILLISECONDS.sleep(6);
for (int i = 0; i < taskCount; i++) {
// 模拟消费者出队操作比生产者入队操作慢
TimeUnit.MILLISECONDS.sleep(3);
log.info("Consumer ({}) take: ({}).", Thread.currentThread().getName(), this.take());
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
};
public LogDemoProducerAndConsumer(BlockingQueue<Integer> blockingQueue) {
this.blockingQueue = blockingQueue;
}
public void logDemoProducerAndConsumer() throws InterruptedException {
final Thread p1 = new Thread(producerRunnable, "p1");
p1.start();
final Thread c1 = new Thread(consumerRunnable, "c1");
c1.start();
p1.join();
c1.join();
}
private void put(Integer e) throws InterruptedException {
blockingQueue.put(e);
}
private Integer take() throws InterruptedException {
return blockingQueue.take();
}
}
各个BlockingQueue的Demo结果
ArrayBlockingQueue
@Test
public void arrayBlockingQueueTest() throws InterruptedException {
new LogDemoProducerAndConsumer(new ArrayBlockingQueue<>(8)).logDemoProducerAndConsumer();
}
执行结果如下, 出队的顺序跟入队的顺序有关.
Producer (p1) put: (0).
Producer (p1) put: (1).
Producer (p1) put: (2).
Producer (p1) put: (3).
Producer (p1) put: (4).
Producer (p1) put: (5).
Consumer (c1) take: (0).
Producer (p1) put: (6).
Producer (p1) put: (7).
Consumer (c1) take: (1).
Producer (p1) put: (8).
Producer (p1) put: (9).
Producer (p1) put: (10).
Consumer (c1) take: (2).
Producer (p1) put: (11).
Consumer (c1) take: (3).
Consumer (c1) take: (4).
Producer (p1) put: (12).
Producer (p1) put: (13).
Consumer (c1) take: (5).
Consumer (c1) take: (6).
Producer (p1) put: (14).
Producer (p1) put: (15).
Consumer (c1) take: (7).
Consumer (c1) take: (8).
Consumer (c1) take: (9).
Consumer (c1) take: (10).
Consumer (c1) take: (11).
Consumer (c1) take: (12).
Consumer (c1) take: (13).
Consumer (c1) take: (14).
Consumer (c1) take: (15).
LinkedBlockingQueue
@Test
public void linkedBlockingQueueTest() throws InterruptedException {
new LogDemoProducerAndConsumer(new LinkedBlockingQueue<>(8)).logDemoProducerAndConsumer();
}
执行结果如下, 出队的顺序跟入队的顺序有关.
Producer (p1) put: (0).
Producer (p1) put: (1).
Producer (p1) put: (2).
Producer (p1) put: (3).
Producer (p1) put: (4).
Consumer (c1) take: (0).
Producer (p1) put: (5).
Producer (p1) put: (6).
Consumer (c1) take: (1).
Producer (p1) put: (7).
Producer (p1) put: (8).
Consumer (c1) take: (2).
Producer (p1) put: (9).
Producer (p1) put: (10).
Consumer (c1) take: (3).
Producer (p1) put: (11).
Consumer (c1) take: (4).
Producer (p1) put: (12).
Consumer (c1) take: (5).
Producer (p1) put: (13).
Consumer (c1) take: (6).
Producer (p1) put: (14).
Consumer (c1) take: (7).
Producer (p1) put: (15).
Consumer (c1) take: (8).
Consumer (c1) take: (9).
Consumer (c1) take: (10).
Consumer (c1) take: (11).
Consumer (c1) take: (12).
Consumer (c1) take: (13).
Consumer (c1) take: (14).
Consumer (c1) take: (15).
PriorityBlockingQueue
@Test
public void priorityBlockingQueueTest() throws InterruptedException {
// 更改默认优先级,手动设置为: 数字越大, 优先级越大, 更先被消费
new LogDemoProducerAndConsumer(new PriorityBlockingQueue<>(8, Comparator.reverseOrder()))
.logDemoProducerAndConsumer();
}
执行结果如下, 可以看到, 在优先级队列PriorityBlockingQueue
中, 出队的顺序跟优先级有关, 即通过compareTo
方法比较.
Producer (p1) put: (0).
Producer (p1) put: (1).
Producer (p1) put: (2).
Producer (p1) put: (3).
Producer (p1) put: (4).
Producer (p1) put: (5).
Producer (p1) put: (6).
Producer (p1) put: (7).
Consumer (c1) take: (7).
Producer (p1) put: (8).
Producer (p1) put: (9).
Producer (p1) put: (10).
Consumer (c1) take: (10).
Producer (p1) put: (11).
Producer (p1) put: (12).
Consumer (c1) take: (12).
Producer (p1) put: (13).
Producer (p1) put: (14).
Consumer (c1) take: (14).
Producer (p1) put: (15).
Consumer (c1) take: (15).
Consumer (c1) take: (13).
Consumer (c1) take: (11).
Consumer (c1) take: (9).
Consumer (c1) take: (8).
Consumer (c1) take: (6).
Consumer (c1) take: (5).
Consumer (c1) take: (4).
Consumer (c1) take: (3).
Consumer (c1) take: (2).
Consumer (c1) take: (1).
Consumer (c1) take: (0).
SynchronousQueue
@Test
public void synchronousBlockingQueueTest() throws InterruptedException {
// 没有队列容量, 一个线程put, 必须另一个线程take之后, 才能进行put
new LogDemoProducerAndConsumer(new SynchronousQueue<>()).logDemoProducerAndConsumer();
}
执行结果如下, 可以看到, 在SynchronousQueue
中, 一个生产者线程put一个元素后, 必须要有另一个消费者线程take之后, 生产者才能继续put, 否则被阻塞.
Producer (p1) put: (0).
Consumer (c1) take: (0).
Consumer (c1) take: (1).
Producer (p1) put: (1).
Consumer (c1) take: (2).
Producer (p1) put: (2).
Producer (p1) put: (3).
Consumer (c1) take: (3).
Consumer (c1) take: (4).
Producer (p1) put: (4).
Producer (p1) put: (5).
Consumer (c1) take: (5).
Consumer (c1) take: (6).
Producer (p1) put: (6).
Producer (p1) put: (7).
Consumer (c1) take: (7).
Consumer (c1) take: (8).
Producer (p1) put: (8).
Producer (p1) put: (9).
Consumer (c1) take: (9).
Producer (p1) put: (10).
Consumer (c1) take: (10).
Consumer (c1) take: (11).
Producer (p1) put: (11).
Consumer (c1) take: (12).
Producer (p1) put: (12).
Consumer (c1) take: (13).
Producer (p1) put: (13).
Producer (p1) put: (14).
Consumer (c1) take: (14).
Consumer (c1) take: (15).
Producer (p1) put: (15).