并发集合-BlockingQueue系列

BlockingQueue

1. BlockingQueue 概述

BlockingQueue 是 Java 并发包 (java.util.concurrent) 中的一个接口,代表一个线程安全的阻塞队列

它扩展了 Queue 接口,并提供了阻塞插入和移除操作:

  • 当队列满时,插入操作会被阻塞,直到队列有空闲空间。
  • 当队列空时,移除操作会被阻塞,直到队列有可用元素。

核心方法

方法 说明
put(E e) 阻塞插入,队列满时等待
take() 阻塞移除,队列空时等待
offer(E e, long timeout, TimeUnit unit) 超时插入,队列满时等待一段时间
poll(long timeout, TimeUnit unit) 超时移除,队列空时等待一段时间
add(E e) 非阻塞插入,队列满时抛异常
remove() 非阻塞移除,队列空时抛异常
offer(E e) 非阻塞插入,队列满时返回 false
poll() 非阻塞移除,队列空时返回 null

2. BlockingQueue 主要实现类

Java 提供了多种 BlockingQueue 实现,适用于不同场景:

(1) ArrayBlockingQueue

源码分析

public class ArrayBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {
  
  	final Object[] items; // 存放元素的数组

    int takeIndex; // 下一次出队元素的索引

    int putIndex; // 下一次入队元素的索引

    int count; // 元素个数

    final ReentrantLock lock; // 锁

    private final Condition notEmpty; // 锁的条件变量

    private final Condition notFull; // 锁的条件变量
  
  	// 构造方法(带容量)
		public ArrayBlockingQueue(int capacity) {
        this(capacity, false); // 默认非公平锁
    }
  
  	// 构造方法(带容量的同时指定是否公平)
		public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }
  
  	// 构造方法
		public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) { }
  
  	// 内部添加元素的方法(私有)
		private void enqueue(E x) {
        final Object[] items = this.items;
        items[putIndex] = x; // 保存元素
        if (++putIndex == items.length) // 1,每次元素入队 putIndex +1;2,当队列满了 putIndex 置为 0
            putIndex = 0; // 置为0的原因:形成环形数组,使得数组可以循环利用
        count++; // 元素个数+1
        notEmpty.signal(); // 唤醒一个等待 notEmpty 条件的一个线程(条件队列,唤醒消费者)
    }
  
  	// 内部移除元素的方法(私有)
    private E dequeue() {
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex]; // 先得到要移除的元素
        items[takeIndex] = null; // 数组位置元素置为空
        if (++takeIndex == items.length) // 同 putIndex,也要每次移除累加,也要形成环形数组
            takeIndex = 0;
        count--; // 元素个数-1
        if (itrs != null) // 维护迭代器状态(如果有活跃的迭代器)
            itrs.elementDequeued(); // 通知迭代器元素已被移除
        notFull.signal(); // 唤醒一个等待 notFull 条件的一个线程(条件队列,唤醒生产者)
        return x; // 返回移除的元素
    }
  
  	// 阻塞入队
		public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length) // count 是元素个数,items 是存储元素的数组。当队列满了就 await() 阻塞
                notFull.await();
            enqueue(e); // 队列未满就添加
        } finally {
            lock.unlock();
        }
    }
  
  	// 阻塞出队
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) // 当数组为空时阻塞住
                notEmpty.await();
            return dequeue(); // 队列不为空就移除元素并返回移除的元素
        } finally {
            lock.unlock();
        }
    }
  
}

特性总结

  • 基于数组实现的有界阻塞队列(初始化时必须指定容量)。
  • FIFO(先进先出) 顺序。
  • 默认非公平锁(可配置为公平锁)。
  • 固定大小,不能扩容

(2) LinkedBlockingQueue

源码分析

public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {

    private final int capacity; // 队列大小

    private final AtomicInteger count = new AtomicInteger(); // 元素个数

    transient Node<E> head; // 头结点
  
    private transient Node<E> last; // 尾节点

    private final ReentrantLock takeLock = new ReentrantLock(); // 出队锁

    private final Condition notEmpty = takeLock.newCondition(); // 出队锁条件变量

    private final ReentrantLock putLock = new ReentrantLock(); // 入队锁

    private final Condition notFull = putLock.newCondition(); // 入队锁条件变量
  
  	 // 节点内部类
     static class Node<E> {
        E item;

        Node<E> next;

        Node(E x) { item = x; }
    }
  
  	// 构造方法,默认无界队列(其实是有界的,Integer 最大值)
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
  
  	// 构造方法,可以指定队列大小
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity; // 队列大小
        last = head = new Node<E>(null); // 初始就会创建一个虚拟节点,头结点和尾节点都会指向这个节点
    }
  
  	// 构造方法
    public LinkedBlockingQueue(Collection<? extends E> c) { }
  
  	// 内部添加元素的方法(私有)
  	private void enqueue(Node<E> node) {
        last = last.next = node;
    }
  
  	// 内部移除元素的方法(私有)
    private E dequeue() {
        Node<E> h = head;
        Node<E> first = h.next;
        h.next = h; // help GC
        head = first;
        E x = first.item;
        first.item = null; // 新的头结点要作为虚拟节点,同 AQS 虚拟节点的线程要置为空的思想一致
        return x;
    }
  
  	// 阻塞入队
    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        int c = -1;
        Node<E> node = new Node<E>(e); // 入队的元素包装成 Node
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();  // 获取入队锁
        try {
            while (count.get() == capacity) { // 如果队列满了就阻塞(线程进入条件队列)
                notFull.await();
            }
            enqueue(node); // 队列未满,元素入队
            c = count.getAndIncrement(); // 入队后维护元素个数。c = 旧值,count+=1
            if (c + 1 < capacity) // 队列未满时,唤醒一个等待 notFull 的线程(唤醒生产者,唤醒的不是消费者,生产者唤醒生产者)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0) // 如果原队列为空,这里刚添加了一个元素,唤醒一个消费者
            signalNotEmpty();
    }
  
  	// 阻塞出队
    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly(); // 获取出队锁
        try {
            while (count.get() == 0) { // 如果队列为空阻塞住
                notEmpty.await();
            }
            x = dequeue(); // 队列不为空,元素出队,并得到出队的元素(移除都节点,第二个节点作为新的虚拟头结点)
            c = count.getAndDecrement(); // c = 旧的元素个数,count-1
            if (c > 1) // 如果队列还有元素,唤醒一个消费者(这里也是消费者唤醒消费者)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity) // 如果操作前队列是满的,唤醒一个消费者
            signalNotFull();
        return x; // 返回移除的元素
    }
  	
}

特性总结

  • 基于链表实现的可选有界阻塞队列(默认 Integer.MAX_VALUE,可指定大小)
  • FIFO 顺序
  • 吞吐量通常比 ArrayBlockingQueue(两把锁分离读写)
  • 固定大小,不会扩容

(3) PriorityBlockingQueue

  • 基于堆实现的无界优先级阻塞队列(默认自然排序,可自定义 Comparator)。
  • 不保证 FIFO,按优先级出队。
  • 适用于任务调度(如高优先级任务优先处理)
BlockingQueue<Integer> queue = new PriorityBlockingQueue<>();
queue.put(3);
queue.put(1); // 1 会比 3 先出队
int num = queue.take(); // 1

(4) SynchronousQueue

  • 不存储元素的阻塞队列(容量为 0)。
  • 插入操作必须等待移除操作(配对机制)。
  • 适用于直接传递任务(如 Executors.newCachedThreadPool
BlockingQueue<Integer> queue = new SynchronousQueue<>();
// 生产者线程
new Thread(() -> {
    queue.put(1); // 阻塞,直到消费者调用 take()
}).start();

// 消费者线程
new Thread(() -> {
    int num = queue.take(); // 取出 1
}).start();

(5) DelayQueue

  • 基于 PriorityQueue 的无界延迟队列(元素需实现 Delayed 接口)。
  • 元素只有到期(getDelay() <= 0)才能被取出
  • 适用于定时任务、缓存过期
import java.util.concurrent.*;

// 1. 创建一个延迟元素(实现Delayed接口)
class SimpleDelayedItem implements Delayed {
    private final String data;
    private final long expireTime; // 到期时间(纳秒)

    public SimpleDelayedItem(String data, long delayMs) {
        this.data = data;
        this.expireTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(delayMs, TimeUnit.MILLISECONDS);
    }

    // 2. 实现getDelay()方法(返回剩余延迟时间)
    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(expireTime - System.nanoTime(), TimeUnit.NANOSECONDS);
    }

    // 3. 实现compareTo()方法(用于排序)
    @Override
    public int compareTo(Delayed o) {
        return Long.compare(this.expireTime, ((SimpleDelayedItem)o).expireTime);
    }

    @Override
    public String toString() {
        return data;
    }
}

public class SimpleDelayQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 4. 创建DelayQueue
        DelayQueue<SimpleDelayedItem> queue = new DelayQueue<>();
        
        // 5. 添加延迟元素(2秒、1秒、3秒后到期)
        queue.put(new SimpleDelayedItem("Item1", 2000));
        queue.put(new SimpleDelayedItem("Item2", 1000));
        queue.put(new SimpleDelayedItem("Item3", 3000));

        // 6. 取出元素(按到期顺序)
        System.out.println(queue.take()); // 1秒后输出 Item2
        System.out.println(queue.take()); // 再等1秒输出 Item1
        System.out.println(queue.take()); // 再等1秒输出 Item3
    }
}

3. 如何选择合适的 BlockingQueue?

场景 推荐实现
固定大小的任务缓冲 ArrayBlockingQueue
高吞吐量任务队列 LinkedBlockingQueue
优先级任务调度 PriorityBlockingQueue
直接传递任务(无缓冲) SynchronousQueue
延迟/定时任务 DelayQueue

4. 总结

  • BlockingQueue 是线程安全的阻塞队列,适用于生产者-消费者模型。
  • ArrayBlockingQueueLinkedBlockingQueue 最常用,前者有界,后者可选有界,后者两把锁并发量更高。
  • PriorityBlockingQueueDelayQueue 适用于特殊排序需求
  • SynchronousQueueLinkedTransferQueue 适用于直接交互场景
posted @ 2023-05-28 18:56  CyrusHuang  阅读(37)  评论(0)    收藏  举报