介绍

阻塞队列(BlockingQueue)是指当队列满时,队列会阻塞插入元素的线程,直到队列不满;当队列空时,队列会阻塞获得元素的线程,直到队列变非空。阻塞队列就是生产者用来存放元素、消费者用来获取元素的容器。

当线程 插入/获取 动作由于队列 满/空 阻塞后,队列也提供了一些机制去处理,或抛出异常,或返回特殊值,或者线程一直等待...

方法/处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e, timeout, unit)
移除方法 remove(o) poll() take() poll(timeout, unit)
检查方法 element() peek() — 不移除元素 不可用 不可用

tips: 如果是无界阻塞队列,则 put 方法永远不会被阻塞;offer 方法始终返回 true。

Java 中的阻塞队列:

ArrayBlockingQueue

ArrayBlockingQueue 是一个用数组实现的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序,默认情况下不保证线程公平的访问。

通过可重入的独占锁 ReentrantLock 来控制并发,Condition 来实现阻塞。

public class ArrayBlockingQueueTest {      /**      * 1. 由于是有界阻塞队列,需要设置初始大小      * 2. 默认不保证阻塞线程的公平访问,可设置公平性      */     private static ArrayBlockingQueue<String> QUEUE = new ArrayBlockingQueue<>(2, true);      public static void main(String[] args) throws InterruptedException {          Thread put = new Thread(() -> {             // 3. 尝试插入元素             try {                 QUEUE.put("java");                 QUEUE.put("javaScript");                 // 4. 元素已满,会阻塞线程                 QUEUE.put("c++");             } catch (InterruptedException e) {                 e.printStackTrace();             }         });         put.start();         Thread take = new Thread(() -> {             try {                 // 5. 获取一个元素                 System.out.println(QUEUE.take());             } catch (InterruptedException e) {                 e.printStackTrace();             }         });         take.start();         // 6 javaScript、c++         System.out.println(QUEUE.take());         System.out.println(QUEUE.take());     } }

LinkedBlockingQueue

LinkedBlockingQueue 是一个用单向链表实现的有界阻塞队列。此队列的默认和最大长度为 Integer.MAX_VALUE。此队列按照先进先出的原则对元素进行排序。

和 ArrayBlockingQueue 一样,采用 ReentrantLock 来控制并发,不同的是它使用了两个独占锁来控制消费和生产,通过 takeLock 和 putLock 两个锁来控制生产和消费,互不干扰,只要队列未满,生产线程可以一直生产;只要队列不空,消费线程可以一直消费,不会相互因为独占锁而阻塞。

tips:因为使用了双锁,避免并发计算不准确,使用了一个 AtomicInteger 变量统计元素总量。

LinkedBlockingDeque

LinkedBlockingDeque 是一个由双向链表结构组成的有界阻塞队列,可以从队列的两端插入和移出元素。它实现了BlockingDeque接口,多了addFirst、addLast、offerFirst、offerLast、peekFirst和peekLast等方法,以 First 单词结尾的方法,表示插入、获取或移除双端队列的第一个元素。以 Last 单词结尾的方法,表示插入、获取或移除双端队列的最后一个元素。

LinkedBlockingDeque 的 Node 实现多了指向前一个节点的变量 prev,以此实现双向队列。并发控制上和 ArrayBlockingQueue 类似,采用单个 ReentrantLock 来控制并发。因为双端队列头尾都可以消费和生产,所以使用了一个共享锁。

双向阻塞队列可以运用在“工作窃取”模式中。

public class LinkedBlockingDequeTest {      private static LinkedBlockingDeque<String> DEQUE = new LinkedBlockingDeque<>(2);      public static void main(String[] args) {         DEQUE.addFirst("java");         DEQUE.addFirst("c++");         // java         System.out.println(DEQUE.peekLast());         // java         System.out.println(DEQUE.pollLast());         DEQUE.addLast("php");         // c++         System.