揭开Java中最智能的队列——PriorityBlockingQueue(优先级阻塞队列)的神秘面纱!这次我们把它想象成一家"智能急诊医院",病人不是先来先治,而是按病情严重程度(优先级)决定救治顺序。准备好进入这个充满堆排序、动态扩容和优先级较量的世界了吗?🏥⚕️
🏥 故事舞台:智能急诊医院 (PriorityBlockingQueue)
医院特点:
- VIP优先制:病人按病情严重程度(优先级)排序,最紧急的(优先级最高)最先救治
- 动态扩容病房:当病房不够时,医院会自动扩建(数组扩容)
- 院长坐镇:只有一位院长有万能钥匙🔑 (
ReentrantLock lock),所有关键操作都要他批准 - 急救广播系统:当有病人来时,通过广播 (
notEmpty) 通知医生
核心设备:
java
Copy
// 病房楼(数组实现的小顶堆)
private transient Object[] queue;
// 当前病人数
private transient int size;
// 病情评估系统(Comparator决定谁更优先)
private transient Comparator<? super E> comparator;
// 院长的万能钥匙(全局锁)
private final ReentrantLock lock;
// 急救广播(Condition通知医生)
private final Condition notEmpty;
// 扩建许可证(0/1表示是否正在扩建)
private transient volatile int allocationSpinLock;
📌 关键设计: 医院用了一种叫 "小顶堆" (Min-Heap) 的神奇结构管理病房,保证病情最紧急(优先级数值最小)的病人永远在1号病房(堆顶)!
🎬 场景一:病人挂号入院(offer/put操作)
java
Copy
public boolean offer(E e) {
if (e == null) throw new NullPointerException(); // 不收治"隐形病人"
final ReentrantLock lock = this.lock;
lock.lock(); // 1. 院长亲自上锁(不可中断锁)
int n, cap;
Object[] array;
// 2. 检查病房是否已满?
while ((n = size) >= (cap = (array = queue).length))
tryGrow(array, cap); // 3. 满了就扩建病房!
try {
Comparator<? super E> cmp = comparator;
if (cmp == null) // 4. 没有病情评估系统?按默认规则
siftUpComparable(n, e, array);
else // 5. 有评估系统?按系统规则
siftUpUsingComparator(n, e, array, cmp);
size = n + 1; // 6. 病人数+1
notEmpty.signal(); // 7. 广播:"有病人!"
} finally {
lock.unlock(); // 8. 院长解锁
}
return true;
}
// 神奇扩建术
private void tryGrow(Object[] array, int oldCap) {
lock.unlock(); // ⚡ 神奇操作:先释放锁!(允许其他操作继续)
Object[] newArray = null;
// 用"扩建许可证"确保只有一个工队在扩建
if (allocationSpinLock == 0 &&
UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset, 0, 1)) {
try {
// 计算新病房数量:小医院翻倍+2,大医院扩50%
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) :
(oldCap >> 1));
if (newCap > MAX_ARRAY_SIZE) // 处理超大医院
newCap = hugeCapacity(oldCap);
newArray = new Object[newCap]; // 建新病房楼
} finally {
allocationSpinLock = 0; // 归还许可证
}
}
// 其他工队主动让出资源
Thread.yield();
lock.lock(); // 重新上锁
if (newArray != null && queue == array) {
queue = newArray; // 切换新病房楼
System.arraycopy(array, 0, newArray, 0, oldCap); // 搬运病人
}
}
🤒 故事细节:
-
心脏病人张三(优先级1)来挂号,被拒收"隐形病人"(非空检查)
-
院长亲自坐镇(
lock.lock()) -
关键检查:当前病人数
size>= 病房数queue.length?- ✅ 病房充足:直接入住
- ❌ 病房已满:启动神奇扩建术
-
扩建魔术四步曲:
-
先解锁:院长暂时离开(
lock.unlock()),让医生继续救治病人 -
抢许可证:工队用
CAS抢"扩建许可证"(allocationSpinLock 0→1) -
建新楼:
- 小医院(<64病房):
新容量 = 旧容量*2 + 2 - 大医院:
新容量 = 旧容量*1.5
- 小医院(<64病房):
-
搬病人:工队把病人搬到新病房楼(
System.arraycopy)
-
-
VIP病房分配术:
java
Copy
private static <T> void siftUpComparable(int k, T x, Object[] array) {
Comparable<? super T> key = (Comparable<? super T>) x;
while (k > 0) {
int parent = (k - 1) >>> 1; // 找父病房
Object e = array[parent];
if (key.compareTo((T) e) >= 0) break; // 病情不如父?停止
array[k] = e; // 父病人移到当前病房
k = parent; // 当前病房设为父病房
}
array[k] = key; // 找到最终位置
}
-
新病人先住进末尾病房(
k = size) -
向上冒泡比较:
- 找父病房:
父索引 = (k-1)/2 - 比较病情(优先级):如果比父病人严重,就交换位置
- 重复直到找到合适位置
- 找父病房:
- 更新病人数+1,院长广播通知医生(
notEmpty.signal()) - 院长离场解锁(
lock.unlock())
💡 示例:已有病人[3(轻伤),5(中伤),8(重伤)],新来优先级2的病人:
- 先放末尾:[3,5,8,2]
- 2 vs 父(8):2>8? 交换 → [3,5,2,8]
- 2 vs 新父(3):2>3? 交换 → [2,5,3,8]
- 形成新小顶堆 → 最紧急的2在堆顶
🎬 场景二:医生救治病人(poll/take操作)
java
Copy
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // 1. 医生申请院长钥匙(可中断)
E result;
try {
// 2. 循环等待直到有病人
while ((result = dequeue()) == null)
notEmpty.await(); // 3. 没病人?听广播等待
} finally {
lock.unlock(); // 4. 归还钥匙
}
return result;
}
private E dequeue() {
int n = size - 1;
if (n < 0) return null; // 没病人
Object[] array = queue;
E result = (E) array[0]; // 1. 取1号病房病人(最紧急)
E x = (E) array[n]; // 2. 取最后一个病人
array[n] = null; // 3. 清空末尾病房
// 4. 堆结构调整
Comparator<? super E> cmp = comparator;
if (cmp == null)
siftDownComparable(0, x, array, n);
else
siftDownUsingComparator(0, x, array, n, cmp);
size = n; // 5. 更新病人数
return result;
}
// 堆结构下沉调整
private static <T> void siftDownComparable(int k, T x, Object[] array, int n) {
Comparable<? super T> key = (Comparable<? super T>) x;
int half = n >>> 1; // 最后一个非叶子节点
while (k < half) {
int child = (k << 1) + 1; // 左子病房
Object c = array[child];
int right = child + 1;
// 选更紧急的子病人
if (right < n && ((Comparable<T>) c).compareTo((T) array[right]) > 0)
c = array[child = right];
if (key.compareTo((T) c) <= 0) break; // 比子病人都急?停止
array[k] = c; // 子病人上移
k = child; // 下移到子病房
}
array[k] = key; // 放置到最终位置
}
👨⚕️ 故事细节:
-
李医生申请院长钥匙(
lock.lockInterruptibly()) -
核心救治操作:
- 取1号病房病人(堆顶,最紧急)
- 把最后一个病人移到临时位置
- 堆结构调整术:
java
Copy
// 示例:取走堆顶[2]后,把末尾8放到堆顶
原始堆: [2, 5, 3, 8] → 取走2 → 8放堆顶 → [8,5,3]
// 调整过程:
1. 8 vs 子节点(5,3): 选更紧急的3
2. 8>3? 交换 → [3,5,8]
3. 3 vs 子节点(8): 3<8? 停止
最终堆: [3,5,8]
-
向下筛选三步骤:
- 找左右子病房:
左子=2*k+1,右子=2*k+2 - 选更紧急(优先级更小)的子病人
- 如果当前病人不如子病人紧急,就交换位置
- 重复直到合适位置
- 找左右子病房:
- 如果没病人(
dequeue返回null),医生听广播等待(notEmpty.await()) - 医生归还钥匙(
lock.unlock())
⚕️ 核心技术揭秘
1. 堆数据结构(Heap)
java
Copy
// 父子节点关系
parent(i) = (i-1)/2
leftChild(i) = 2*i + 1
rightChild(i) = 2*i + 2
// 堆特性
array[parent(i)] <= array[i] // 小顶堆
-
完全二叉树:病房严格从左到右排列
-
堆序性:父病房的病人一定比子病房的紧急(优先级更高)
-
高效操作:
- 插入:O(log n)(最坏从叶到根)
- 取最小:O(1)
- 删除最小:O(log n)(最坏从根到叶)
2. 动态扩容策略
| 场景 | 扩容公式 | 示例(旧容量→新容量) |
|---|---|---|
| 小医院(<64) | old + old + 2 | 10 → 22 |
| 大医院 | old * 1.5 | 100 → 150 |
| 极限情况 | Integer.MAX_VALUE - 8 | 接近int最大值 |
3. 并发控制三要素
-
全局锁(单锁):
final ReentrantLock lock- 插入/删除/扩容时独占访问
-
CAS扩容许可:
allocationSpinLock- 用
Unsafe.compareAndSwapInt确保只有一个线程扩容
- 用
-
条件通知:
final Condition notEmpty- 只在插入元素后队列非空时通知
4. 堆调整算法
java
Copy
// 上浮(插入时)
while (有父节点 && 当前<父节点) {
交换(当前, 父节点);
当前位置 = 父位置;
}
// 下沉(删除时)
while (当前位置<非叶节点) {
找最紧急的子节点;
if (当前<=子节点) break;
交换(当前, 子节点);
当前位置 = 子位置;
}
📊 PBQ 与其他队列对比
| 特性 | PriorityBlockingQueue (智能医院) | ArrayBlockingQueue (奶茶店) | LinkedBlockingQueue (快递中心) |
|---|---|---|---|
| 数据结构 | 动态扩容堆 | 固定循环数组 | 链表 |
| 顺序 | 按优先级 | FIFO | FIFO |
| 锁机制 | 单全局锁 | 单锁 | 双锁(put/take分离) |
| 扩容 | ✅ 1.5倍动态扩容 | ❌ 固定 | ✅ 节点无限扩展 |
| 时间复杂度 | 插入/删除: O(log n) | 插入/删除: O(1) | 插入/删除: O(1) |
| 内存占用 | 数组+堆结构 | 连续数组 | 每个节点额外24字节 |
| 最佳场景 | 任务调度、实时系统 | 固定缓冲区 | 高并发生产消费 |
💉 智能急诊医院特色服务
1. 定制化病情评估(Comparator)
java
Copy
// 创建VIP优先的医院(数值越小越优先)
PriorityBlockingQueue<Patient> hospital =
new PriorityBlockingQueue<>(11, (p1, p2) -> {
// 优先抢救心脏病人
if(p1.isHeartAttack() && !p2.isHeartAttack()) return -1;
if(!p1.isHeartAttack() && p2.isHeartAttack()) return 1;
// 其次看病情等级
return Integer.compare(p1.getLevel(), p2.getLevel());
});
2. 批量接诊(drainTo)
java
Copy
// 把病情>=4的病人转到ICU
List<Patient> icuList = new ArrayList<>();
hospital.drainTo(icuList, 10, p -> p.getLevel() >= 4);
3. 扩容时仍可救治(精妙设计)
java
Copy
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return dequeue(); // 即使正在扩容,也能取数据!
} finally {
lock.unlock();
}
}
- 扩容期间:其他线程仍可执行出队操作
- 设计关键:
tryGrow()中的lock.unlock()释放锁
💡 核心要点总结
-
堆是心脏:数组实现的二叉堆保证O(1)取最小元素
-
动态扩容是生存之道:
- 小容量:翻倍+2
- 大容量:1.5倍
- CAS控制单线程扩容
-
全局锁是院长:单锁保证线程安全,简化设计
-
堆调整是核心算法:
- 上浮(siftUp):新病人从底向上比较
- 下沉(siftDown):堆顶空缺后从顶向下筛选
-
无界队列:理论上可扩容到
Integer.MAX_VALUE -
优先级为王:不是FIFO,而是按Comparator或自然顺序
下次使用PriorityBlockingQueue时,就想象这家智能急诊医院:院长(lock)掌控全局,护士用堆结构(siftUp)安排病房,医生按堆顶顺序救治(siftDown),工队动态扩建病房(tryGrow),而急救广播(notEmpty)确保没有病人被遗漏。这才是处理优先级任务的终极解决方案!🚑🏆