个人理解

总体认知

  • 本质上是队列,但是并不一定是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

  • 基于数组实现的有界阻塞队列,初始化会分配指定sizeObject数组
  • 入队操作和出队操作都是使用的同一把锁
  • 出队顺序是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).

Java阻塞队列简介的更多相关文章

  1. lesson2:java阻塞队列的demo及源码分析

    本文向大家展示了java阻塞队列的使用场景.源码分析及特定场景下的使用方式.java的阻塞队列是jdk1.5之后在并发包中提供的一组队列,主要的使用场景是在需要使用生产者消费者模式时,用户不必再通过多 ...

  2. 细说并发5:Java 阻塞队列源码分析(下)

    上一篇 细说并发4:Java 阻塞队列源码分析(上) 我们了解了 ArrayBlockingQueue, LinkedBlockingQueue 和 PriorityBlockingQueue,这篇文 ...

  3. Java阻塞队列(BlockingQueue)实现 生产者/消费者 示例

    Java阻塞队列(BlockingQueue)实现 生产者/消费者 示例 本文由 TonySpark 翻译自 Javarevisited.转载请参见文章末尾的要求. Java.util.concurr ...

  4. Java 并发系列之七:java 阻塞队列(7个)

    1. 基本概念 2. 实现原理 3. ArrayBlockingQueue 4. LinkedBlockingQueue 5. LinkedBlockingDeque 6. PriorityBlock ...

  5. Java阻塞队列四组API介绍

    Java阻塞队列四组API介绍 通过前面几篇文章的学习,我们已经知道了Java中的队列分为阻塞队列和非阻塞队列以及常用的七个阻塞队列.如下图: 本文来源:凯哥Java(kaigejava)讲解Java ...

  6. java阻塞队列

    对消息的处理有些麻烦,要保证各种确认.为了确保消息的100%发送成功,笔者在之前的基础上做了一些改进.其中要用到多线程,用于重复发送信息. 所以查了很多关于线程安全的东西,也看到了阻塞队列,发现这个模 ...

  7. Java阻塞队列的实现

    阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞.试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列 ...

  8. java阻塞队列与非阻塞队列

    在并发编程中,有时候需要使用线程安全的队列.如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法. //使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入 ...

  9. (原创)JAVA阻塞队列LinkedBlockingQueue 以及非阻塞队列ConcurrentLinkedQueue 的区别

    阻塞队列:线程安全 按 FIFO(先进先出)排序元素.队列的头部 是在队列中时间最长的元素.队列的尾部 是在队列中时间最短的元素.新元素插入到队列的尾部,并且队列检索操作会获得位于队列头部的元素.链接 ...

  10. java阻塞队列之ArrayBlockingQueue

    在Java的java.util.concurrent包中定义了和多线程并发相关的操作,有许多好用的工具类,今天就来看下阻塞队列.阻塞队列很好的解决了多线程中数据的安全传输问题,其中最典型的例子就是客园 ...

随机推荐

  1. hashicorp/raft模块实现的raft集群存在节点跨集群身份冲突问题

    我通过模块github.com/hashicorp/raft使用golang实现了一个raft集群功能,发现如下场景中会遇到一个问题: 测试启动如下2个raft集群,集群名称,和集群node与IP地址 ...

  2. length与capacity

    package javaBasic; public class DifferenceLengthCapacity { public static void main(String[] args) { ...

  3. .NET 的全新低延时高吞吐自适应 GC - Satori GC

    GC 的 STW 问题 GC,垃圾回收器,本质上是一种能够自动管理自己分配的内存的生命周期的内存分配器.这种方法被大多数流行编程语言采用,然而当你使用垃圾回收器时,你会失去对应用程序如何管理内存的控制 ...

  4. post 报头注入

    1.user-agent 注入: 使用情况:万能密码无法绕过安全验证,用户名无法注入,通过查看源代码分析执行的动作. bp抓包修改user_agent的数据如下 User-Agent: ' or ud ...

  5. frp实现内网穿透访问内网多台Linux服务器

    本文主要记录笔者在使用frp实现内网穿透访问内网多台Linux服务器的全过程,包括公网服务器的配置.frp服务端.客户端的下载与配置,以及配置systmctl来实现系统级启停frp,并记录我遇到的一些 ...

  6. Vite代理配置不生效问题

    1.问题: 在写Vite+vue3.0项目时,配置vite代理,遇到不起效的问题,具体如下: // vite.config.ts proxy: { '/api': ' http://localhost ...

  7. 3. LangChain4j-RAG,实现简单的text-sql功能

    1. 简介 前两章我们讲了如何使用LangChain4J进行AI交互, 其中包括 使用ChatLanguageModel.ChatMessage.ChatMemory等底层组件进行灵活/自由的与AI交 ...

  8. 不写代码,让 AI 生成手机 APP!保姆级教程

    你现在看到的 APP,是我完全用 AI 生成的,一行代码都没写!怎么做到的呢? 大家好,我是程序员鱼皮.AI 发展很快,现在随随便便就能生成一个网站,但是怎么纯用 AI 开发能在手机上运行的 APP ...

  9. 如何给 GitHub Copilot "洗脑”,让 AI 精准遵循指令产出高质量代码

    引子 最近在项目中使用 GitHub Copilot 的过程中,我发现了一个很有趣的现象. 当我让 Copilot 帮我写代码时,它总是热情满满地给出一大段实现.但当我仔细审视这些代码时,却经常会发现 ...

  10. Excel Micro (VBA)