参考文献

JUC Collections 集合框架

img

Queue

Blocking Queue阻塞队列

  • java.util.concurrent.ArrayBlockingQueue:最基础且开发中最常用的阻塞队列,底层采用数组实现的有界队列,初始化需要指定队列的容量.

    • ArrayBlockingQueue 是如何保证线程安全的呢?它内部是使用了一个重入锁ReentrantLock,并搭配 notEmpty、notFull 两个条件变量Condition来控制并发访问.从队列读取数据时,如果队列为空,那么会阻塞等待,直到队列有数据了才会被唤醒.如果队列已经满了,也同样会进入阻塞状态,直到队列有空闲才会被唤醒.
  • java.util.concurrent.LinkedBlockingQueue:内部采用的数据结构是链表,队列的长度可以是有界或者无界的.

    • 初始化不需要指定队列长度,默认是Integer.MAX_VALUE.LinkedBlockingQueue 内部使用了takeLock、putLock两个重入锁ReentrantLock,以及notEmpty、notFull两个条件变量Condition 来控制并发访问.采用读锁和写锁的好处是可以避免读写时相互竞争锁的现象,所以相比于 ArrayBlockingQueue,LinkedBlockingQueue 的性能要更好.
  • java.util.concurrent.PriorityBlockingQueue:采用最小堆实现的优先级队列,队列中的元素按照优先级进行排列,每次出队都是返回优先级最高的元素.

    • PriorityBlockingQueue 内部是使用了一个ReentrantLock 以及一个条件变量Condition ,notEmpty来控制并发访问,不需要notFull是因为PriorityBlockingQueue 是无界队列,所以每次put都不会发生阻塞.PriorityBlockingQueue底层的最小堆是采用数组实现的,当元素个数大于等于最大容量时会触发扩容,在扩容时会先释放锁,保证其他元素可以正常出队,然后使用CAS操作确保只有一个线程可以执行扩容逻辑.
  • java.util.concurrent.DelayQueue,一种支持延迟获取元素的阻塞队列,常用于缓存、定时任务调度等场景.

    • DelayQueue 内部是采用优先级队列PriorityQueue存储对象.DelayQueue中的每个对象都必须实现 Delayed接口,并重写compareTogetDelay方法.向队列中存放元素的时候必须指定延迟时间,只有延迟时间已满的元素才能从队列中取出.
  • java.util.concurrent.SynchronousQueue,又称无缓冲队列.比较特别的是SynchronizedQueue 内部不会存储元素.

    • ArrayBlockingQueue、LinkedBlockingQueue不同,SynchronizedQueue直接使用CAS操作控制线程的安全访问.其中puttake操作都是阻塞的,每一个put操作都必须阻塞等待一个take操作,反之亦然.所以 SynchronizedQueue 可以理解为生产者和消费者配对的场景,双方必须互相等待,直至配对成功.在 JDK 的线程池Executors.newCachedThreadPool 中就存在SynchronousQueue的运用,对于新提交的任务,如果有空闲线程,将重复利用空闲线程处理任务,否则将新建线程进行处理.
  • java.util.concurrent.LinkedTransferQueue,一种特殊的无界阻塞队列,可以看作 LinkedBlockingQueuesSynchronousQueue(公平模式)、ConcurrentLinkedQueue 的合体.

    • SynchronousQueue不同的是,LinkedTransferQueue 内部可以存储实际的数据,当执行put 操作时,如果有等待线程,那么直接将数据交给对方,否则放入队列中.与LinkedBlockingQueues 相比,LinkedTransferQueue使用CAS无锁操作进一步提升了性能.
全类名 底层实现 有无界 使用场景
java.util.concurrent.ArrayBlockingQueue 数组 有界 适合有固定大小的任务队列,常用于生产者-消费者模型
java.util.concurrent.LinkedBlockingQueue 链表 有界 适用于大多数生产者-消费者场景,提供可选的容量限制
java.util.concurrent.PriorityBlockingQueue 数组 无界 用于优先级任务调度,元素按照优先级顺序出队
java.util.concurrent.DelayQueue 链表 无界 用于任务延迟执行,只有延迟期满的元素才能从队列中取出
java.util.concurrent.SynchronousQueue / / 不存储元素,适用于传递型设计,任务直接交接,常用于线程池中的任务提交
java.util.concurrent.LinkedTransferQueue 链表 无界 用于高效的传输场景,可在没有消费者时等待元素被消费
java.util.concurrent.LinkedBlockingDeque 链表 有界 适用于需要双端操作的生产者-消费者场景,允许在队列两端插入和移除元素
接口/行为 队列满抛出异常 返回特定值 一直阻塞 阻塞一段时间
新增元素 add(e) offer(e) put(e) offer(e,time,unit)
删除元素 remove() poll() take() poll(time,unit)
查看元素 element() peek() / /

非阻塞队列

  • java.util.concurrent.ConcurrentLinkedQueue,它是一个采用双向链表实现的无界并发非阻塞队列,它属于 LinkedQueue 的安全版本.ConcurrentLinkedQueue内部采用 CAS 操作保证线程安全,这是非阻塞队列实现的基础,相比 ArrayBlockingQueue、LinkedBlockingQueue 具备较高的性能.
  • java.util.concurrent.ConcurrentLinkedDeque,也是一种采用双向链表结构的无界并发非阻塞队列.与 ConcurrentLinkedQueue 不同的是,ConcurrentLinkedDeque 属于双端队列,它同时支持 FIFO 和 FILO 两种模式,可以从队列的头部插入和删除数据,也可以从队列尾部插入和删除数据,适用于多生产者和多消费者的场景.

第三方无锁队列

Disruptor
JCTools
  • JCTools 是适用于 JVM 并发开发的工具,主要提供了一些 JDK 确实的并发数据结构,例如非阻塞 Map、非阻塞 Queue 等。其中非阻塞队列可以分为四种类型,可以根据不同的场景选择使用。

    • Spsc 单生产者单消费者;

    • Mpsc 多生产者单消费者;

    • Spmc 单生产者多消费者;

    • Mpmc 多生产者多消费者。