java集合详解 - PriorityBlockingQueue 详解

Python3.8

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

PriorityBlockingQueue 是 Java 并发包 (java.util.concurrent) 中的一个线程安全的优先级队列实现。它结合了 PriorityQueue 的优先级排序特性和 BlockingQueue 的线程安全阻塞特性。

目录

基本特性

底层数据结构

基于二叉堆的数组实现

动态扩容机制

并发控制机制

1. 主要锁结构 

2. 自旋锁辅助扩容 

阻塞的核心实现机制

1. 锁和条件变量

2. 阻塞发生的两种情况 

(1) 取元素时阻塞(队列为空) 

 (2) 等待锁时阻塞

唤醒机制

为什么没有 put 阻塞?

中断处理

与 ArrayBlockingQueue 的阻塞对比

实际阻塞示例 

 输出

总结


基本特性

  1. 无界队列:理论上可以无限增长(受限于内存),但可以设置初始容量

  2. 线程安全:内部使用 ReentrantLock 保证线程安全

  3. 阻塞操作:当队列为空时,获取操作会阻塞;由于是无界队列,插入操作不会阻塞

  4. 优先级排序:元素按照自然顺序或 Comparator 指定的顺序出队

底层数据结构

基于二叉堆的数组实现

PriorityBlockingQueue 内部使用一个平衡的二叉堆(通常是最小堆)来实现优先级队列,使用数组作为底层存储:

private transient Object[] queue; // 存储元素的数组
  • 堆结构保证第一个元素总是优先级最高(最小)的元素

  • 对于位置 n 的元素:

    • 左子节点在 2*n+1

    • 右子节点在 2*n+2

    • 父节点在 (n-1)/2

动态扩容机制

由于是无界队列,当数组容量不足时会自动扩容: 

  • 初始容量默认为 11 (DEFAULT_INITIAL_CAPACITY)

  • 当队列满时自动扩容,每次扩容约 50%(newCapacity = oldCapacity + (oldCapacity < 64 ? oldCapacity + 2 : oldCapacity >> 1)

  • 最大容量为 Integer.MAX_VALUE - 8(数组头保留空间)

  • 扩容时释放主锁,避免阻塞其他操作

  • 使用 CAS 操作控制扩容过程

  • 小容量时加倍增长,大容量时增长50%

private void tryGrow(Object[] array, int oldCap) {
    lock.unlock(); // 扩容前先释放锁,允许其他操作进行
    Object[] newArray = null;
    if (allocationSpinLock == 0 &&
        UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset, 0, 1)) {
        try {
            // 计算新容量:小于64时加倍,否则增长50%
            int newCap = oldCap + ((oldCap < 64) ?
                               (oldCap + 2) :
                               (oldCap >> 1));
            if (newCap - MAX_ARRAY_SIZE > 0) {    // 处理溢出
                int minCap = oldCap + 1;
                if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
                    throw new OutOfMemoryError();
                newCap = MAX_ARRAY_SIZE;
            }
            newArray = new Object[newCap];
        } finally {
            allocationSpinLock = 0;
        }
    }
    // 其他线程可能也在扩容,让出CPU
    if (newArray == null)
        Thread.yield();
    lock.lock(); // 重新获取锁
    if (newArray != null && queue == array) {
        queue = newArray;
        System.arraycopy(array, 0, newArray, 0, oldCap);
    }
}

并发控制机制

1. 主要锁结构 

private final ReentrantLock lock; // 主锁,控制所有访问
private final Condition notEmpty; // 用于take操作的等待条件
  • 所有修改操作(put/take等)都需要获取这个锁

  • 读操作(如peek)也需要获取锁保证可见性

2. 自旋锁辅助扩容 

private transient volatile int allocationSpinLock; // 扩容自旋锁

使用轻量级的自旋锁(通过CAS操作)控制扩容过程,避免在扩容期间完全阻塞其他操作。

阻塞的核心实现机制

1. 锁和条件变量

private final ReentrantLock lock;         // 主锁
private final Condition notEmpty;         // 取元素时的等待条件
// 注意:没有notFull条件,因为是无界队列

2. 阻塞发生的两种情况 

(1) 取元素时阻塞(队列为空) 

当消费者线程调用 take() 方法时,如果队列为空,线程会被阻塞: 

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();  // 可中断的获取锁
    try {
        E result;
        // 循环检查直到获取到元素
        while ((result = dequeue()) == null) {
            notEmpty.await();  // 释放锁并等待
        }
        return result;
    } finally {
        lock.unlock();
    }
}

阻塞过程

  1. 线程获取锁

  2. 检查队列是否为空(dequeue()返回null)

  3. 如果为空,调用 notEmpty.await()

    • 原子性地释放锁

    • 线程进入等待状态

    • 被唤醒后会重新获取锁

  4. 循环检查直到成功取出元素

 (2) 等待锁时阻塞

多个线程同时操作队列时,未获取到锁的线程会在锁层面阻塞:

lock.lockInterruptibly();  // 这里可能阻塞

唤醒机制

public boolean offer(E e) {
    // ...省略其他代码...
    notEmpty.signal();  // 唤醒一个等待的消费者线程
    return true;
}

唤醒过程

  1. 生产者线程获取锁并成功插入元素

  2. 调用 notEmpty.signal() 唤醒一个等待的消费者线程

  3. 被唤醒的消费者线程会尝试重新获取锁

  4. 获取锁成功后继续执行 take() 中的循环检查

为什么没有 put 阻塞?

因为 PriorityBlockingQueue 是无界队列,所以:

  • put() 方法实际上直接调用了 offer()

  • 不需要 notFull 条件变量

  • 插入操作永远不会阻塞(除非达到 Integer.MAX_VALUE)

中断处理

PriorityBlockingQueue 支持响应中断:

lock.lockInterruptibly();  // 可被中断的锁获取
notEmpty.await();          // 可被中断的条件等待

当线程在阻塞状态被中断时:

  1. 会抛出 InterruptedException

  2. 线程的中断状态会被清除

  3. 锁会被正确释放

与 ArrayBlockingQueue 的阻塞对比

特性PriorityBlockingQueueArrayBlockingQueue
队列边界无界有界
put() 是否阻塞永不阻塞队列满时阻塞
take() 是否阻塞队列空时阻塞队列空时阻塞
使用的条件变量只有 notEmptynotEmpty 和 notFull
锁数量单个锁单个锁

实际阻塞示例 

PriorityBlockingQueue<Integer> queue = new PriorityBlockingQueue<>();

// 消费者线程
new Thread(() -> {
    try {
        System.out.println("尝试取元素...");
        Integer item = queue.take();  // 这里会阻塞
        System.out.println("获取到: " + item);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

// 模拟3秒后生产者添加元素
Thread.sleep(3000);
queue.put(42);  // 唤醒消费者

 输出

尝试取元素...
(3秒后)
获取到: 42

总结

PriorityBlockingQueue 的阻塞特性:

  1. 单向阻塞:只在取元素时(队列为空)阻塞

  2. 条件等待:通过 notEmpty.await() 实现优雅等待

  3. 自动唤醒:插入元素时自动唤醒等待的消费者

  4. 线程安全:所有操作由同一把锁保护

  5. 无界特性:插入操作永不阻塞(可能引发OOM需要注意)

这种设计使其非常适合需要按优先级处理任务的生产者-消费者场景,同时避免了有界队列的复杂性。

HashMap详解  HashLink详解 ArrayList详解 CopyOnWriteArrayList详解 HashSet详解 LinkedHashMap详解 LinkedHashSet详解 TreeSet详解 TreeMap详解 ConcurrentHashMap详解 PriorityBlockingQueue 详解 PriorityQueue 详解

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值