基本队列Queue接口
的 子
接口
public interface BlockingQueue<E> extends Queue<E> {}
扩展了Queue
接口,提供了线程安全的阻塞操作,线程安全的阻塞操作:线程安全的阻塞是指在多线程环境中,为了保证数据的一致性和完整性,系统会阻止某些线程继续执行,直到某个条件得到满足
主要特性包括:
因为阻塞队列BlockingQueue接口的父接口是基本队列Queue接口,所以拥有基本队列的方法,除此之外又有部分新增方法
添加元素节点方法
//插入元素,成功返回 true;队列满时抛出 IllegalStateException
boolean add(E e);
//插入元素,成功返回 true;失败返回 false
boolean offer(E e);
//添加元素 如果队列满则阻塞
void put(E e) throws InterruptedException;
//添加元素 队列满时阻塞指定时间
boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException;
移除元素节点方法
//移除指定元素 如队列为空抛出异常
boolean remove(Object o);
//移除并返回元素 如果队列空则阻塞
E take() throws InterruptedException;
// 移除并返回元素 成功返回元素,失败返回 null
E poll(long timeout, TimeUnit unit)throws InterruptedException;
//批量移除 移除所有可用元素到指定集合,直到BlockingQueue为空或者达到Collection的容量限制
int drainTo(Collection<? super E> c);
//批量移除 移除最多maxElements元素到指定集合
int drainTo(Collection<? super E> c, int maxElements);
查找元素方法
//剩余容量 返回队列剩余空间
int remainingCapacity();
//继承自Queue接口方法 返回队首节点 如果队列为空时抛出 NoSuchElementException
E element();
//继承自Queue接口方法 返回队首节点 如果队列为空时返回 null(推荐)
E peek();
判断方法
// 判断是否包含某个节点元素
public boolean contains(Object o);
ArrayBlockingQueue实现类架构 继承抽象队列实现队列的通用默认实现方法,实现阻塞队列接口
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
三个核心构造方法,都要求指定队列的容量 (capacity)。容量一旦设定,不能动态改变
ArrayBlockingQueue(int capacity)
创建一个具有指定固定容量
的 ArrayBlockingQueue, 默认使用非公平的访问策略
// 创建一个容量为 10 的非公平 ArrayBlockingQueue
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
ArrayBlockingQueue(int capacity, boolean fair)
创建一个具有指定固定容量
的 ArrayBlockingQueue,并允许显式指定公平性 (fair)
// 创建一个容量为 10 的公平 ArrayBlockingQueue
ArrayBlockingQueue<String> fairQueue = new ArrayBlockingQueue<>(10, true);
ArrayBlockingQueue(int capacity, boolean fair, Collection extends E> c)
创建一个具有指定固定容量
和公平性
设置的 ArrayBlockingQueue,并在创建时初始化队列内容
。队列的初始元素来自指定的集合
List<String> initialItems = Arrays.asList("Apple", "Banana", "Cherry");
// 创建一个容量为 5 的公平队列,并用 initialItems 初始化
// 注意:initialItems.size() (3) 必须 <= capacity (5)
ArrayBlockingQueue<String> preloadedQueue = new ArrayBlockingQueue<>(5, true, initialItems);
看名字ArrayBlockingQueue 数组阻塞队列,所以其数据结构是 数组+队列
数据结构
final Object[] items; // 存储元素的数组
int takeIndex; // 下一个要取元素的位置
int putIndex; // 下一个要放元素的位置
int count; // 当前元素数量
final ReentrantLock lock; // 主锁
private final Condition notEmpty; // 非空条件
private final Condition notFull; // 非满条件
这里面除了存储元素的数组 和 通过 位置指向索引之外 还有两个核心锁机制
ReentrantLock详情描述 :任一ArrayBlockingQueue操作管理都在获取到可重入锁(互斥独占锁)
ReentrantLock之后执行,同步阻塞线程安全的管理机制,这个锁管理机制核心特性:
1.可重入:如果一个线程已经持有某个锁,再次请求这个锁时会立即成功,并增加一个内部的计数器
2.独占互斥性:不同线程不能同时持有锁
Condition 详情描述:它的管理对象 已持有锁的线程(二次加锁)
环形数组结构管理过程
// 创建容量为5的阻塞队列
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
初始状态:this.items = new Object[capacity];
Object[] items 数组容量大小为3,每个索引位置上元素初始话为null
初始状态: count=0, takeIndex=0, putIndex=0 items 容量为3的空数组
[ null, null, null]
↑takeIndex
↑putIndex
操作: put("A")
→ putIndex=0 写入"A"
→ putIndex=(0+1)%3=1
→ count=1
状态: [A, null, null] | takeIndex=0, putIndex=1
操作: put("B")
→ putIndex=1 写入"B"
→ putIndex=(1+1)%3=2
→ count=2
状态: [A, B, null] | takeIndex=0, putIndex=2
操作: take()
→ 取 takeIndex=0 的"A"
→ takeIndex=(0+1)%3=1
→ count=1
状态: [null, B, null] | takeIndex=1, putIndex=2
操作: put("C")
→ putIndex=2 写入"C"
→ putIndex=(2+1)%3=0 // 回绕到数组开头 所以叫环绕数组管理
→ count=2
状态: [null, B, C] | takeIndex=1, putIndex=0
操作: take()
→ 取 takeIndex=1 的"B"
→ takeIndex=(1+1)%3=2
→ count=1
状态: [null, null, C] | takeIndex=2, putIndex=0
1. 插入元素
方法 | 行为 |
---|---|
add(E e) |
队列未满时插入并返回 true;队列满时抛出 IllegalStateException |
offer(E e) |
队列未满时插入并返回 true;队列满时立即返回 false |
put(E e) |
队列满时阻塞等待,直到有空间插入 |
offer(E e, long timeout, TimeUnit unit) |
队列满时阻塞等待指定时间,超时返回 false |
2. 移除元素
方法 | 行为 |
---|---|
remove() |
移除并返回头部元素;队列空时抛出 NoSuchElementException |
poll() |
移除并返回头部元素;队列空时立即返回 null |
take() |
队列空时阻塞等待,直到有元素可移除 |
poll(long timeout, TimeUnit unit) |
队列空时阻塞等待指定时间,超时返回 null |
3. 检查元素
方法 | 行为 |
---|---|
element() |
返回头部元素;队列空时抛出异常 |
peek() |
返回头部元素;队列空时返回 null |
1.有界容量
a.创建时必须指定队列容量
,一旦创建,容量不可更改
b.防止资源耗尽(避免生产者生产速度远超消费者时导致内存溢出,也防止消费过快生产跟不上)
// 创建容量为3的阻塞队列
BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
// 生产者线程 vs 消费者线程
Thread producer = new Thread(() -> {
queue.put("E"); // 阻塞给消费者消费直到有空间...}
Thread consumer = new Thread(() -> {
queue.take(); // 阻塞取出生产者时间填满队列...}
2.线程安全:同步阻塞
a.所有操作使用同一把锁 ReentrantLock 保证线程安全
b.通过两个 Condition(notEmpty 和 notFull)实现生产者-消费者协调
3.阻塞操作
a.队列满时,生产者线程会被阻塞直到有空间
b.队列空时,消费者线程会被阻塞直到有元素
已出队作简单示例
// 移除元素
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();//同一个 ReentrantLock 保证线程安全
try {
while (count == 0)//队列空时阻塞等待,直到有元素可移除
//使用Condition.await()条件阻塞等待 当前线程释放ReentrantLock锁给其余线程
notEmpty.await();
return dequeue();//出队操作
} finally {
lock.unlock();
}
}
//出队操作
private E dequeue() {
final Object[] items = this.items;
E x = (E) items[takeIndex]; // 取出 takeIndex 处元素
items[takeIndex] = null; // 置空原位置
if (++takeIndex == items.length) takeIndex = 0; // 循环数组处理
count--;
notFull.signal(); // 唤醒等待的生产者线程
return x;
}
//入队操作也是类似 使用 同一和对象锁ReentrantLock 判断队列是否notFull决定是否阻塞等待
生产者流程:
✅ 获取 入队锁putLock
✅ 队列未满 → 入队 → 唤醒消费者 → 释放锁
✅ 队列已满 → notFull.await() → 自动释放锁 → 阻塞
消费者流程:
✅ 获取 ReentrantLock
✅ 队列不空 → 出队 → 唤醒生产者 → 释放锁
✅ 队列已空 → notEmpty.await() → 自动释放锁 → 阻塞
需要注意的是 ArrayBlockingQueue的是同步阻塞的线程安全,同步阻塞(Synchronous Blocking):
1.在等待操作完成的过程中,当前线程被阻塞(挂起),不能做其他事情
2.必须等待该操作完成才能继续执行后续代码
已这个为示例当队列空时,调用take的线程会调用notEmpty.await()释放ReentrantLock锁挂起该线程,
释放锁:这样其他线程(比如生产者)就可以获取到这把锁
而释放锁之后 生产者线程拿到ReentrantLock锁开始执行 直到元素空间用完满了,调用take的线程会调用notfull.await()释放ReentrantLock锁挂起生产者线程,然后锁又给到消费者线程,就这样相互执行
流程简介:
空间空的-->ReentrantLock给到生产者线程执行 到空间满了-->停下 释放ReentrantLock
给到消费者线程 到空间空了--> 停下 释放ReentrantLock给到生产者线程.....
对于生产者线程(调用`put`的线程),当空间满了 会阻塞等待直到队列有空闲空间
整个过程中,线程在同步调用中等待(阻塞),直到操作完成
对于消费者线程(调用`take`的线程),当空间空了 会阻塞等待直到队列有元素,
整个过程中,线程在同步调用中等待(阻塞),直到操作完成
4.公平性可选
a.创建时可选择公平锁(默认非公平)
b.公平锁保证等待时间最长的线程优先访问
在构造方法中使用可选择是否公平锁的策略,这个是针对ReentrantLock锁选择是否公平机制
的
✅ 优点:内存布局紧凑、实现简单、公平性可控
❌ 缺点:固定容量、吞吐量不如双锁队列(同步阻塞嘛 性能不高 不适于大数据吞吐量
)
适用:需要精确控制队列大小且生产/消费频率均衡的场景
使用场景
1.线程池任务队列
// ThreadPoolExecutor 常用工作队列
new ThreadPoolExecutor(
corePoolSize,
maxPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100) // 有界任务队列
);
2.数据传输管道
生产者线程(数据采集)→ ArrayBlockingQueue → 消费者线程(数据处理)
3.流量控制:通过固定队列大小限制系统负载
4.批处理系统:缓冲待处理数据批次
性能优化建议
1.合理设置容量大小
// 根据系统负载设置合适容量
int capacity = Runtime.getRuntime().availableProcessors() * 10;
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(capacity);
2.避免长时间阻塞
// 使用带超时的offer/poll
if (!queue.offer(item, 1, TimeUnit.SECONDS)) {
// 处理添加失败情况
}
3.监控队列大小
// 定期监控队列使用情况
int size = queue.size();
int remaining = queue.remainingCapacity();
double usage = (double) size / (size + remaining);
4.使用双锁替代方案
需要高吞吐量时考虑 LinkedBlockingQueue 或 ConcurrentLinkedQueue
public class ProducerConsumerDemo {
public static void main(String[] args) {
// 创建容量为5的阻塞队列
BlockingQueue<Task> queue = new ArrayBlockingQueue<>(5);
// 生产者
Runnable producer = () -> {
try {
for (int i = 1; i <= 10; i++) {
Task task = new Task("Task-" + i);
queue.put(task); // 队满时阻塞插入 直到队列有空间
System.out.println(Thread.currentThread().getName()
+ " 生产: " + task + " | 队列大小: " + queue.size());
Thread.sleep(200); // 模拟生产耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
// 消费者
Runnable consumer = () -> {
try {
for (int i = 0; i < 10; i++) {
Task task = queue.take(); // 队空时阻塞取出 直到队列有可移除元素
System.out.println(Thread.currentThread().getName()
+ " 消费: " + task + " | 队列大小: " + queue.size());
Thread.sleep(500); // 模拟处理耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
};
// 启动多个生产者和消费者
new Thread(producer, "生产者1").start();
new Thread(producer, "生产者2").start();
new Thread(consumer, "消费者1").start();
new Thread(consumer, "消费者2").start();
}
static class Task {
private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
private final int id;
private final String name;
public Task(String name) {
this.id = ID_GENERATOR.incrementAndGet();
this.name = name;
}
@Override
public String toString() {
return name + "#" + id;
}
}
}
输出示例
生产者1 生产: Task-1#1 | 队列大小: 1
生产者2 生产: Task-1#2 | 队列大小: 2
生产者1 生产: Task-2#3 | 队列大小: 3
生产者2 生产: Task-2#4 | 队列大小: 4
生产者1 生产: Task-3#5 | 队列大小: 5
消费者1 消费: Task-1#1 | 队列大小: 4
消费者2 消费: Task-1#2 | 队列大小: 3
生产者2 生产: Task-3#6 | 队列大小: 4
生产者1 生产: Task-4#7 | 队列大小: 5
消费者1 消费: Task-2#3 | 队列大小: 4
...
基于链表的线程安全阻塞队列
,结合了链表结构的灵活性和阻塞队列的线程安全特性
,是构建生产者-消费者模型的常用工具,是同步阻塞队列
1.创建一个无容量界限大小的LinkedBlockingQueue队列
//
public static final int MAX_VALUE = 0x7fffffff;
public LinkedBlockingQueue() {this(Integer.MAX_VALUE);}
2.创建一个指定容量大小的LinkedBlockingQueue队列
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
//并且初始一个空节点同时作为首尾节点
last = head = new Node<E>(null);}
3.从指定集合创建无界限大小的
public LinkedBlockingQueue(Collection<? extends E> c) {}
//参数:c(要添加到队列的集合)
//创建容量为 Integer.MAX_VALUE 的队列。按集合迭代顺序将元素加入队列
List<String> list = Arrays.asList("A", "B", "C");
BlockingQueue<String> queue = new LinkedBlockingQueue<>(list);
//要求
//集合 c 不能为 null(否则抛出 NullPointerException)
//集合元素不能为 null(否则抛出 NullPointerException)
数据结构:单向链表(Node 节点)
class Node<E> {
E item;
Node<E> next;
}
private final int capacity;//队列容量(可指定,默认`Integer.MAX_VALUE`)
//当前队列元素个数(`AtomicInteger`保证原子性)
private final AtomicInteger count = new AtomicInteger();
//分别指向链表的头节点(哑节点,item为null)和尾节点
transient Node<E> head;
private transient Node<E> last;
// takeLock 和 putLock:分别用于出队和入队的锁
private final ReentrantLock takeLock = new ReentrantLock();
private final ReentrantLock putLock = new ReentrantLock();
//分别由 takeLock和putLock 生成的等待条件
private final Condition notEmpty = takeLock.newCondition();
private final Condition notFull = putLock.newCondition();
技术应用
1.双锁分离
put
、offer
)只获取putLock
take
、poll
)只获取takeLock
两把锁互不干扰,允许生产者和消费者同时操作(除非队列空/满导致阻塞),从而提高并发吞吐量
2.阻塞与唤醒
take
会在notEmpty
条件上等待直到入队操作(如put
)添加元素后唤醒put
会在notFull
条件上等待直到出队操作(如take
)移除元素后唤醒已put方法添加元素简介下流程
public void put(E e) throws InterruptedException {
// 1. 空值检查
if (e == null) throw new NullPointerException();
// 2. 初始化本地变量
int c = -1; // 用于记录插入前的元素数量
Node<E> node = new Node<E>(e); // 创建新节点
final ReentrantLock putLock = this.putLock; // 获取入队锁 出队的同样获取takeLock
final AtomicInteger count = this.count; // 获取原子计数器
// 3. 可中断加锁
putLock.lockInterruptibly(); // 获取锁,允许被中断
try {
// 4. 队列满时等待
while (count.get() == capacity) {
notFull.await(); // 在notFull条件上等待
}
// 5. 执行入队操作
enqueue(node); // 将节点加入链表尾部
// 6. 更新计数器并判断是否需要唤醒其他生产者
c = count.getAndIncrement(); // 获取旧值并自增
if (c + 1 < capacity) // 如果插入后队列未满
notFull.signal(); // 唤醒其他等待的生产者
} finally {
putLock.unlock(); // 确保锁被释放
}
// 7. 唤醒消费者(如果插入前队列为空)
if (c == 0)
signalNotEmpty(); // 唤醒等待的消费者
}
3.状态同步协调 (跨锁通知机制)
通过Condition
对象notEmpty
(队列非空条件)和 notFull
(队列非满条件)实现线程阻塞与唤醒
c == capacity
表示取出前队列是满的),会调用signalNotFull()
(内部获取putLock
后唤醒notFull
上的生产者)。c == 0
表示添加前队列为空),会调用signalNotEmpty()
(内部获取takeLock
后唤醒notEmpty
上的消费者)// 唤醒消费者(需获取 takeLock)
private void signalNotEmpty() {
takeLock.lock();
try {
notEmpty.signal();//队列非空情况下 唤醒消费者移除元素
} finally {
takeLock.unlock();
}
}
// 唤醒生产者(需获取putLock)
private void signalNotFull() {
putLock.lock();
try {
notFull.signal();//队列非满情况下 唤醒生产者添加元素
} finally {
putLock.unlock();
}
}
数据管理机制
入队流程(以put
为例)
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly(); // 获取入队锁
try {
while (count.get() == capacity) { // 队列满则等待
notFull.await();
}
enqueue(node); // 将节点添加到链表尾部
c = count.getAndIncrement(); // 计数增加(返回旧值)
if (c + 1 < capacity) // 如果添加后队列未满,唤醒其他生产者
notFull.signal();
} finally {
putLock.unlock();
}
//因为生产者消费者持有两把锁,所以生产者线程不能直接调用notEmpty.signal()
//因为需要持有与 条件变量关联的锁,即takeLock而是通过signalNotEmpty()方法间接完成
if (c == 0) // 如果添加前队列为空,唤醒消费者(需获取takeLock)
signalNotEmpty();
}
生产者流程:putLock的释放与入队操作绑定,与队列状态无关
✅ 获取 入队锁putLock
✅ 队列已满 → notFull.await() → 自动释放锁 → 阻塞
✅ 队列未满 → 入队 → 条件性选择唤醒其他消费者 → 释放锁
✅ 必然释放 putLock(无论是否入队成功)finally {}
✅ 最后唤醒在notEmpty条件上等待的消费者线程,告诉它们现在队列中至少有一个元素可以消费了
出队流程(以take
为例)
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(); // 移除头节点(实际取head.next)
c = count.getAndDecrement(); // 计数减少(返回旧值)
if (c > 1) // 如果取出前队列元素多于1个,唤醒其他消费者
notEmpty.signal();
} finally {
takeLock.unlock();
}
//因为生产者消费者持有两把锁,所以消费者线程不能直接调用notFull.signal()
//因为需要持有与 条件变量关联的锁,即putLock而是通过signalNotFull()方法间接完成
if (c == capacity) // 如果取出前队列满,唤醒生产者(需获取putLock)
signalNotFull();
return x;
}
消费者流程:
✅ 获取 出队锁takeLock
✅ 队列为空 → 条件性选择唤醒生产者notEmpty.await() → 自动释放出队锁 → 阻塞
✅ 队列不为空 → 执行出队操作 → 取出前队列元素多于1个唤醒其他消费 → 释放出队锁
✅ 必然释放 takeLock(无论是否出队成功)finally {}
✅ 最后唤醒在notEmpty条件上等待的生产者线程,告诉它们现在队列中至少有一个空间可以入队
关键辅助方法
//入队操作enqueue 尾插法
private void enqueue(Node<E> node) {
last = last.next = node; // 尾插法
}
//出队操作dequeue 首移除
private E dequeue() {
Node<E> h = head; // 头节点(哑节点)
Node<E> first = h.next; // 第一个实际节点
h.next = h; // 自引用,帮助GC
head = first; // 更新head指向first
E x = first.item; // 获取数据
first.item = null; // 置空,head成为新的哑节点
return x;
}
流程总结
1.生产者入队:
获取 putLock → 检查容量 → 入队 → 选择性唤醒其他生产者 → 释放 putLock
关键:若入队前队列为空,额外唤醒消费者
2.消费者出队:
获取 takeLock → 检查元素 → 出队 → 选择性唤醒其他消费者 → 释放 takeLock
关键:若出队前队列为满,额外唤醒生产者
3.锁释放机制:
await() 自动释放对应锁(生产者释放 putLock,消费者释放 takeLock)
唤醒操作在释放锁后执行,避免死锁风险
1.阻塞操作
方法 | 行为 |
---|---|
put(E e) |
队列满时阻塞,直到有空间插入元素 |
take() |
队列空时阻塞,直到有元素可取出 |
2.非阻塞/超时操作
方法 | 行为 |
---|---|
offer(E e) |
队列满时直接返回 false (不阻塞) |
poll() |
队列空时直接返回 null (不阻塞) |
offer(E e, long timeout, TimeUnit unit) |
限时等待插入,超时返回 false |
poll(long timeout, TimeUnit unit) |
限时等待取出,超时返回 null |
3.检查操作
方法 | 行为 |
---|---|
peek() |
返回队首元素(不移除),队列空时返回 null |
size() |
返回当前元素数量(由于并发,结果可能不精确) |
remainingCapacity() |
返回剩余容量(有界队列有效) |
1.可选容量
有界模式:构造时指定容量(如 new LinkedBlockingQueue<>(100))
无界模式:默认容量为 Integer.MAX_VALUE(约21亿),近似无界(但可能导致内存溢出)
2.FIFO(先进先出)
元素按入队顺序处理,队尾插入,队首移除,符合队列的典型行为
3.线程安全(同步阻塞队列)
内部通过两把ReentrantLock锁分别对应入队锁putLock
和出队锁takeLock
保证线程安全,提升吞吐量支持高并发场景
入队操作和出队只和`入队锁putLock`和`出队锁takeLock`有关,可同时执行生产 消费两个线程提升吞吐量
而ArrayBlockingQueue出入队是同一把锁管理,生产者消费者线程同一时间只能有一个执行
4.阻塞操作
当队列空时,消费者线程会被阻塞,直到有数据加入
当队列满时(若有界),生产者线程会被阻塞,直到有空间可用
条件 阻塞操作 唤醒触发点
队列空 (count=0) take() 生产者调用 enqueue() 后
队列满 (count=capacity) put() 消费者调用 dequeue() 后
常见应用场景
1.生产者-消费者模型
BlockingQueue<Task> queue = new LinkedBlockingQueue<>(100);
// 生产者
public void produce(Task task) throws InterruptedException {queue.put(task)}
// 消费者
public Task consume() throws InterruptedException {return queue.take()}
2.线程池任务队列
Executors.newFixedThreadPool() 默认使用无界 LinkedBlockingQueue
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());}
流量控制
通过有界队列限制系统负载,防止资源耗尽
ArrayBlockingQueue 防止资源耗尽的操作是:是新增或移除元素时候添加不符合执行条件等待等待
注意事项
1.无界队列的风险
默认无界队列可能导致内存溢出(OOM),建议生产环境显式指定容量。
2.公平性
构造可指定公平锁(new LinkedBlockingQueue(capacity, true)),避免线程饥饿,但降低吞吐量
3.性能权衡
链表结构在动态扩容时更灵活,但内存开销更大。
数组结构内存紧凑,但容量固定
适用场景:
1.大多数生产者-消费者场景
2. 高吞吐量需求,比ArrayBlockingQueue吞吐量高
3.需要无界队列时
public class Example {
public static void main(String[] args) {
// 创建有界队列(容量=10)
LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>(10);
// 生产者线程
new Thread(() -> {
try {
for (int i = 0; i < 20; i++) {
String item = "Item-" + i;
queue.put(item); // 队列满时阻塞
System.out.println("Produced: " + item);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
// 消费者线程
new Thread(() -> {
try {
for (int i = 0; i < 20; i++) {
String item = queue.take(); // 队列空时阻塞
System.out.println("Consumed: " + item);
Thread.sleep(1000); // 模拟处理耗时
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
}
线程安全的优先级队列
实现。它结合了优先级排序
和阻塞操作
的特性,适用于多线程环境下需要按优先级处理元素的场景
1.使用默认构造方法
PriorityBlockingQueue<Integer> queue = new PriorityBlockingQueue<>();
2.指定初始容量
PriorityBlockingQueue<Integer> queue = new PriorityBlockingQueue<>(100);
3.指定初始容量和比较器
PriorityBlockingQueue<Integer> queue =
new PriorityBlockingQueue<>(100, Comparator.reverseOrder());
4.从其他集合创建
List<Integer> list = Arrays.asList(5, 3, 8, 1);
PriorityBlockingQueue<Integer> queue = new PriorityBlockingQueue<>(list);
数据结构:单向链表(Node 节点)
使用动态数组存储元素,以二叉堆(通常是最小堆)的形式组织
//1. 存储结构
private transient Object[] queue; // 堆的数组表示
private transient int size; // 当前元素数量
//2. 并发控制
private final ReentrantLock lock = new ReentrantLock();//ReentrantLock实现线程安全
private final Condition notEmpty = lock.newCondition(); // 用于take()阻塞
//3.扩容控制
private static final int DEFAULT_INITIAL_CAPACITY = 11;
private transient volatile int allocationSpinLock; // CAS扩容标志
//4.排序比较器
private transient Comparator<? super E> comparator;
数据结构管理机制
二次堆 最小堆
1 (root)
/ \
3 4
/ \ / \
5 7 9 10
父子关系:索引 i 的元素
父节点:(i-1)/2
左子节点:2*i + 1
右子节点:2*i + 2
堆性质:每个节点的值都小于(最小堆)或大于(最大堆)其子节点
锁机制
使用单个 ReentrantLock
控制写操作(添加/删除),结合Condition(notEmpty)
实现阻塞
获取
public boolean offer(E e) {
if (e == null)throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
//先获取到ReentrantLock 锁 在执行新增节点的操作
入队操作(add/put/offer)
堆上浮(Sift-Up)
while (k > 0) {
int parent = (k - 1) >>> 1; // 计算父节点索引
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0) // 满足堆性质则停止
break;
queue[k] = e; // 父节点下移
k = parent; // 继续向上比较
}
queue[k] = x; // 找到最终位置
出队操作(poll/take)
堆下沉(Sift-Down)
int half = size >>> 1; // 只需比较到非叶子节点
while (k < half) {
int child = (k << 1) + 1; // 左子节点
Object c = queue[child];
int right = child + 1;
// 选择较小的子节点(最小堆)
if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c; // 子节点上移
k = child; // 继续向下比较
}
queue[k] = x; // 找到最终位置
动态扩容机制
1.当 size >= queue.length 时触发:
2.获取 allocationSpinLock 通过 CAS(Compare-And-Swap)
//计算新容量:
int newCap = oldCap + (oldCap < 64 ? oldCap + 2 : oldCap >> 1);
3.创建新数组并迁移数据
4.释放自旋锁
//添加元素
put(E e) 添加元素(永不阻塞,可能扩容)
offer(E e) 添加元素(永不阻塞,返回 true)
//移除元素
take() 阻塞获取并移除队首元素(队列空时阻塞)
poll() 非阻塞获取队首元素(队列空时返回 null)
poll(time, unit) 超时阻塞获取队首元素
//获取元素
peek() 获取但不移除队首元素(队列空时返回 null)
// 容量大小
size() 返回当前元素数量(结果可能因并发不精确)
comparator() 返回使用的比较器(自然排序时为 null)
1.无界队列
队列没有容量限制(最大为 Integer.MAX_VALUE
),所以添加操作(put
、add
、offer
)永远不会因为队列满而阻塞,但可能会因为内存不足而抛出 OutOfMemoryError
。
2.扩容机制
当队列元素数量达到当前数组容量时,会进行扩容。扩容策略是:如果当前容量小于64,则扩容为原来的两倍加2;否则扩容为原来的1.5倍(即增加50%)
3.阻塞获取
当队列为空时,尝试从队列中取出元素的操作(如 take
)会被阻塞,直到队列中有元素可用
4.优先级排序
队列中的元素按照优先级顺序排列,队首(出队)的元素总是优先级最高的(最小或最大取决于排序规则)
默认情况下,元素按自然升序排序(最小堆),也可以通过自定义 Comparator
来改变排序规则
5.线程安全
内部通过一个可重入锁(ReentrantLock
)来保证线程安全,因此它可以在多线程环境中安全使用
注意事项
1.禁止 null 元素
添加 null 会抛出 NullPointerException。
2.非公平锁
默认使用非公平锁
(吞吐量更高),可通过构造函数指定公平锁。
3.遍历无序
iterator() 或 toString() 的遍历结果不保证按优先级顺序(仅保证出队有序)
4.内存风险
作为无界队列,需防止生产者过快导致内存溢出(OutOfMemoryError)
5.元素排序要求
元素必须实现 Comparable 或提供 Comparator,否则抛出 ClassCastException
适用场景
1.任务调度系统(高优先级任务优先执行)
2.事件处理框架(紧急事件优先处理)
// 1. 创建队列(自然排序)
PriorityBlockingQueue<Integer> queue = new PriorityBlockingQueue<>();
// 2. 添加元素
queue.put(30);
queue.offer(10);
queue.add(20); // 实际调用 offer
// 3. 多线程消费
new Thread(() -> {
try {
while (true) {
Integer num = queue.take(); // 队列空时阻塞
System.out.println("Consumed: " + num);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
// 输出顺序:10 → 20 → 30(按升序出队)
3.需要阻塞等待的优先级任务队列
(如线程池任务调度)
// 创建一个实现了Comparable接口的任务类
public class ComparableFutureTask extends FutureTask<Object>
implements Comparable<ComparableFutureTask> {
// 实现compareTo方法来定义任务的优先级
@Override
public int compareTo(ComparableFutureTask other) {
// 根据任务的某些属性来决定优先级
return Integer.compare(this.getPriority(), other.getPriority());
}
}
// 初始化一个ThreadPoolExecutor,将其工作队列设置为PriorityBlockingQueue实例
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.MILLISECONDS,
new PriorityBlockingQueue<Runnable>()
);
// 提交任务到线程池
executor.execute(new ComparableFutureTask(task));
PriorityBlockingQueue 应用管理优先级任务的简单代码,按照优先级最高的先输出
1.定义一个任务类(Task),它包含Runnable和优先级,并实现Comparable接口以便在队列中排序
2.创建PriorityBlockingQueue来存储Task对象
3.创建生产者线程来添加不同优先级的任务
4.创建消费者线程来从队列中取出并执行任务(按优先级顺序)
优先级任务类
// 优先级任务类
record Task(String name, int priority) implements Comparable<Task> {
// 优先级比较规则:数字越小优先级越高(1=最高)
@Override
public int compareTo(Task other) {
return Integer.compare(this.priority, other.priority);
}
void execute() throws InterruptedException {
System.out.printf("[%s] 执行任务: %s (优先级: %d)%n",
Thread.currentThread().getName(), name, priority);
TimeUnit.MILLISECONDS.sleep(500); // 模拟任务执行耗时
}
}
主类:创建队列,启动生产者和消费者线程
public class PriorityTaskScheduler {
public static void main(String[] args) throws InterruptedException {
// 创建优先级阻塞队列(按Task的compareTo规则排序)
PriorityBlockingQueue<Task> taskQueue = new PriorityBlockingQueue<>();
// 创建固定大小的线程池(3个工作线程)
ExecutorService executor = Executors.newFixedThreadPool(3);
// 生产者线程:添加任务到队列
Thread producer = new Thread(() -> {
String[] tasks = {"支付处理", "数据备份", "用户登录", "系统告警", "报表生成"};
int[] priorities = {1, 3, 2, 1, 4}; // 优先级定义
for (int i = 0; i < tasks.length; i++) {
Task task = new Task(tasks[i], priorities[i]);
taskQueue.put(task); // 阻塞方法,但队列无界不会阻塞
System.out.println("✅ 提交任务: " + task);
try {
TimeUnit.MILLISECONDS.sleep(200); // 控制任务提交速度
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
// 消费者线程:从队列取任务并执行
Runnable consumer = () -> {
while (!Thread.currentThread().isInterrupted()) {
try {
Task task = taskQueue.take(); // 队列空时阻塞等待
task.execute();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
};
// 启动任务生产者
producer.start();
// 启动3个任务消费者(工作线程)
for (int i = 0; i < 3; i++) {
executor.submit(consumer);
}
// 等待生产者结束
producer.join();
// 等待队列清空
while (!taskQueue.isEmpty()) {
Thread.sleep(100);
}
// 关闭线程池
executor.shutdownNow();
executor.awaitTermination(2, TimeUnit.SECONDS);
}
}
生产者线程:提交5个不同优先级的任务
支付处理(优先级1)
系统告警(优先级1)
用户登录(优先级2)
数据备份(优先级3)
报表生成(优先级4)
消费者线程:从队列取任务执行(take() 在队列空时自动阻塞)
预期输出:
✅ 提交任务: Task[name=支付处理, priority=1]
✅ 提交任务: Task[name=系统告警, priority=1]
✅ 提交任务: Task[name=用户登录, priority=2]
✅ 提交任务: Task[name=数据备份, priority=3]
✅ 提交任务: Task[name=报表生成, priority=4]
[pool-1-thread-1] 执行任务: 支付处理 (优先级: 1)
[pool-1-thread-2] 执行任务: 系统告警 (优先级: 1) // 同优先级按提交顺序
[pool-1-thread-3] 执行任务: 用户登录 (优先级: 2)
[pool-1-thread-1] 执行任务: 数据备份 (优先级: 3)
[pool-1-thread-2] 执行任务: 报表生成 (优先级: 4)
特点:
适用场景:
示例代码:
// 创建优先级队列(按任务优先级排序)
BlockingQueue<Task> priorityQueue = new PriorityBlockingQueue<>(
11, // 初始容量
Comparator.comparingInt(Task::getPriority).reversed()
);
// 添加不同优先级任务
priorityQueue.put(new Task("Low", 1));
priorityQueue.put(new Task("High", 10));
priorityQueue.put(new Task("Medium", 5));
// 消费者会按优先级顺序处理
new Thread(() -> {
while (true) {
Task task = priorityQueue.take();
System.out.println("处理任务: " + task);
}
}).start();
/* 输出:
处理任务: Task[name=High, priority=10]
处理任务: Task[name=Medium, priority=5]
处理任务: Task[name=Low, priority=1]
*/
DelayQueue无界的阻塞队列。核心特性是:只有当队列中元素的“延迟时间”到期后,才能从队列中取出该元素,DelayQueue的数据结构 :最小堆 可选有界队列
DelayQueue 没有容量界限大小的参数
public DelayQueue() {}
public DelayQueue(Collection<? extends E> c) {this.addAll(c);}
但是需要注意的是 DelayQueue 放入元素的要求:放入 DelayQueue 中的元素必须实现 java.util.concurrent.Delayed 接口。这个接口定义了两个关键方法
//返回与此对象关联的剩余延迟时间(在当前给定的时间单位下)
//当返回值 <= 0 时,表示该元素的延迟已到期
long getDelay(TimeUnit unit)
//用于比较两个 Delayed 对象的顺序,通常基于它们的到期时间
//DelayQueue 内部使用优先级队列,通常是PriorityQueue来存储元素,确保队列头部总是最快到期的元素
int compareTo(Delayed o);
//已此排序决定新加入的节点在二次堆中的节点位置,从而保证执行顺序的优先级
内部数据结构:优先级队列 (PriorityQueue)的最小堆
DelayQueue 内部的核心存储结构是一个 基于堆(通常是最小堆)实现的 PriorityQueue
排序依据: 一般是剩余的延迟时间越往堆顶,(或者说,最先到期)的元素在堆顶
至于节点的新增和删除 和PriorityQueue一样 上浮和下沉
管理机制
可重入锁 (ReentrantLock): 出入队同一把锁管理
条件变量 (Condition):要用于实现 阻塞等待。当消费者线程(调用 take() 或 poll(unit))试图从队列中取出元素,但队列为空或者堆顶元素尚未到期时,线程会在 available 条件上等待。当有新的元素入队(特别是成为新的堆顶)或者有等待线程被唤醒时,会通过这个条件变量来通知(signal)等待的线程
1.入队方法(添加元素)
boolean add(E e) 添加元素(底层调用 offer),队列无界永不失败,成功返回 true
boolean offer(E e) 添加元素(推荐使用),队列无界永不返回 false
void put(E e) 添加元素(因无界永不阻塞),底层调用 offer
offer(E e, long, unit) 添加元素(忽略超时参数,因无界永不阻塞)
2.出队方法(取出元素)
E take() 核心方法!阻塞直到有到期元素,返回并移除队列头部(最快到期元素)
E poll() 非阻塞,立即返回:- 队列空 → 返回 null; 头部未到期 → 返回 null
E poll(long timeout, TimeUnit unit) 限时阻塞等待到期元素,超时返回 null
3.检查方法
E peek() 返回队列头部元素(不移除),可能未到期,队列空返回 null
int size() 返回当前队列元素总数(包含未到期元素)
boolean isEmpty() 检查队列是否为空
4.批量操作
int drainTo(Collection<? super E> c) 转移所有已到期元素到指定集合,返回转移数量
drainTo(Collection<? super E> c, int max) 转移最多 max 个已到期元素到集合
void clear() 清空队列(移除所有元素)
1.延迟获取
核心功能,确保元素只有在指定的延迟时间过后才能被消费
2.线程安全
DelayQueue 是线程安全的,内部使用锁(通常是 ReentrantLock)来保证并发操作的正确性。多个线程可以安全地进行入队和出队操作
3.无界队列
理论上可以存放无限多的元素(受限于内存)。offer 操作永不阻塞(put 和 add 内部也是调用 offer)
4.基于优先级堆
内部使用优先级队列实现,保证出队 (take, poll) 时总是最快到期的元素优先被取出,操作的时间复杂度为 O(log n)
5.阻塞操作
take() 方法提供了阻塞能力,让消费者线程在需要等待元素到期时高效地挂起,避免忙等(busy-waiting)
典型使用场景
1.任务调度/定时器
实现一个简单的单线程或多线程定时任务调度器。将需要延迟执行的任务(封装为Runnable 或 Callable
)包装成实现了Delayed
接口的对象,放入DelayQueue
。工作线程不断从队列中take()
到期任务并执行,常见线程池里面应用
// 创建一个 DelayQueue
DelayQueue<DelayedTask> delayQueue = new DelayQueue<>();
// 创建并添加延迟任务
delayQueue.put(new DelayedTask("Task 1", 2000)); // 2秒后执行
delayQueue.put(new DelayedTask("Task 2", 5000)); // 5秒后执行
delayQueue.put(new DelayedTask("Task 3", 1000)); // 1秒后执行
// 创建一个线程池来处理延迟任务
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 创建一个线程来不断从 DelayQueue 中取出到期的任务并执行
executorService.execute(() -> {
while (true) {
try {
// 从队列中取出到期的任务
DelayedTask task = delayQueue.take();
// 执行任务
System.out.println("Executing task: " + task.getName() +
" at " + System.currentTimeMillis());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
// 关闭线程池
executorService.shutdown();
try {
executorService.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
e.printStackTrace();
}
2.缓存过期清理
自动高效的过期清理机制:存储缓存项时,将缓存项(或其引用)与一个代表过期时间的 Delayed 对象关联放入 DelayQueue。一个专门的清理线程调用 take() 获取到期的项,并将其从主缓存中移除
3.会话/连接超时管理
在需要管理超时的场景(如 Web 会话、网络连接心跳),可以将代表会话或连接的对象包装成 Delayed 对象(超时时间设为最大不活动间隔),放入 DelayQueue。一个监控线程取出到期对象执行,意味着该会话/连接超时,可以执行关闭或清理操作
//DelayQueue存储所有被包装的会话对象 带到期时间节点的
while (!Thread.currentThread().isInterrupted()) {
try {
// 阻塞等待,直到有对象到期
SessionWrapper expiredWrapper = delayQueue.take();
// 获取到期的实际对象
Session expiredSession = expiredWrapper.getTarget();
// 执行超时处理逻辑(关闭连接、清理会话、触发回调等)
handleTimeout(expiredSession);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
break; // 优雅退出
}
}
4.重试机制
实现带有延迟的重试策略。失败的任务可以包装成一个带有递增重试延迟的 Delayed 对象放回队列,等待下次尝试
注意事项
1.元素要求: 放入DelayQueue 中的元素必须实现Delayed
接口实现getDelay(TimeUnit unit)
和compareTo(Delayed o)
getDelay(TimeUnit unit)
根据当前时间计算剩余延迟。存储一个未来的绝对时间点triggerTime
,然后计算剩余延迟时间 triggerTime - System.nanoTime()
,返回的剩余延迟必须 <= 0 时才算到期
compareTo(Delayed o) 方法
: 必须与getDelay()
一致,即到期时间越早(剩余延迟越小)的对象,在排序中应被认为“越小”(compareTo
返回负数),这样才能放在优先级队列的头部。
2.元素状态:DelayQueue
本身不关心元素的内容,只关心其延迟状态。任务的实际执行逻辑(如 Runnable.run()
)需要你在取出元素后自行调用
3.线程管理: take() 会阻塞线程。通常需要创建一个或多个专门的消费者线程来处理队列。确保有优雅关闭这些线程的机制(如使用 interrupt()
)
4.无界队列的风险: 虽然方便,但如果生产者速度远大于消费者速度,可能导致内存耗尽 (OutOfMemoryError
),在需要限制容量的场景,可能需要自定义或寻找替代方案
// 1. 定义一个任务元素,实现 Delayed 接口
class DelayedTask implements Delayed, Runnable {
private final long triggerTime; // 执行任务的时间点 (纳秒)
private final String taskName;
//放入DelayQueue的元素DelayedTask
public DelayedTask(long delayInMillis, String name) {
this.triggerTime = System.nanoTime() + TimeUnit.NANOSECONDS.convert(delayInMillis, TimeUnit.MILLISECONDS);
this.taskName = name;
}
@Override
public long getDelay(TimeUnit unit) {
long remaining = triggerTime - System.nanoTime();
// 将剩余时间转换为请求的单位
return unit.convert(remaining, TimeUnit.NANOSECONDS);
}
@Override
public int compareTo(Delayed other) {
return Long.compare(this.triggerTime, ((DelayedTask) other).triggerTime);
}
@Override
public void run() {
System.out.println("执行任务: " + taskName + " at " + System.currentTimeMillis());
}
}
public class DelayQueueDemo {
public static void main(String[] args) throws InterruptedException {
// 2. 创建 DelayQueue
DelayQueue<DelayedTask> queue = new DelayQueue<>();
// 3. 添加不同延迟的任务
queue.add(new DelayedTask(5000, "任务A (5秒)")); // 5秒后执行
queue.add(new DelayedTask(1000, "任务B (1秒)")); // 1秒后执行
queue.add(new DelayedTask(3000, "任务C (3秒)")); // 3秒后执行
// 4. 创建消费者线程 (可以多个)
Thread consumerThread = new Thread(() -> {
while (true) { // 通常会有停止条件
try {
DelayedTask task = queue.take(); // 阻塞直到有到期任务
task.run(); // 执行到期任务
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
break; // 中断则退出循环
}
}
});
consumerThread.start();
// 主线程等待一段时间观察输出 (实际应用中消费者线程会持续运行)
Thread.sleep(6000);
consumerThread.interrupt(); // 停止消费者线程
consumerThread.join();
}
}
特点:
适用场景:
示例代码:
class DelayedItem implements Delayed {
private final String data;
private final long expireTime;
public DelayedItem(String data, long delayMs) {
this.data = data;
this.expireTime = System.currentTimeMillis() + delayMs;
}
@Override
public long getDelay(TimeUnit unit) {
long diff = expireTime - System.currentTimeMillis();
return unit.convert(diff, TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(this.expireTime, ((DelayedItem) o).expireTime);
}
@Override
public String toString() {
return data;
}
}
// 使用示例
DelayQueue<DelayedItem> delayQueue = new DelayQueue<>();
// 添加延迟元素
delayQueue.put(new DelayedItem("Task1", 3000)); // 3秒后过期
delayQueue.put(new DelayedItem("Task2", 1000)); // 1秒后过期
delayQueue.put(new DelayedItem("Task3", 5000)); // 5秒后过期
// 消费者
new Thread(() -> {
while (true) {
try {
DelayedItem item = delayQueue.take();
System.out.println(System.currentTimeMillis() + " 处理: " + item);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
/* 输出(示例):
1640000001000 处理: Task2
1640000003000 处理: Task1
1640000005000 处理: Task3
*/
特殊的阻塞队列
。它的核心特性使其在并发编程中适用于特定的场景,尤其是需要严格的生产者-消费者线程配对
和直接传
递的场景
1.创建一个默认使用非公平(Non-Fair) 策略的SynchronousQueue
//内部采用 栈(LIFO - 后进先出) 结构 (TransferStack) 来管理等待的线程
public SynchronousQueue() {this(false);}
2.创建一个指定策略的SynchronousQueue
//如果 fair == true:内部采用 队列(FIFO - 先进先出) 结构 (TransferQueue)。
//如果 fair == false:内部采用 栈(LIFO) 结构 (TransferStack)
public SynchronousQueue(boolean fair) {
transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
}
SynchronousQueue不存储元素
,但是它内部确实有节点SNode
SynchronousQueue永远不会在队列中长时间持有等待被消费的元素
(不像ArrayBlockingQueue或LinkedBlockingQueue那样内部有个缓冲区(缓存队列元素))
它的设计目标是直接传递
:一个生产者线程(put/offer)必须等待一个消费者线程(take/poll)即时接收它提供的元素,反之亦然
但是在生产者和消费者线程交接的过程中,需要一个中转协调点
,类似接力棒
,是一个信号
SNode节点
SNode的角色:线程匹配的临时载体
static final class SNode {
//下一个SNode 节点
volatile SNode next;
//匹配状态(int match):用于标识节点是否已成功匹配
volatile SNode match;
//线程自身(Thread waiter):表示哪个线程在等待
volatile Thread waiter;
//要传递的数据(Object item)如果是生产者,这就是它要提供的元素;
//如果是消费者,这个字段通常是null或一个特殊标记(REQUEST),表示它在等待接收数据
Object item;
//标识该节点代表的是生产者(DATA)还是消费者(REQUEST)
int mode;
SNode的生命周期
1.创建:SNode 里面并没有属性字段是存储元素的
2.等待:创建节点后,线程会将自己(通过创建节点)放入等待结构(栈或队列)中,并可能进入阻塞状态(park)
3.匹配
当一个配对的线程(生产者遇到消费者,或消费者遇到生产者)到来时,它会扫描等待结构(栈顶或队头),找到可以匹配的节点
4.传递 匹配成功时
a.生产者的item字段的值会被直接设置到消费者的item字段(或者通过其他方式直接传递)
b.匹配状态(match)被设置
关键点:元素本身并没有被“存储”在SNode中等待后续消费, 匹配成功后,数据是直接从生产者线程传递给了消费者线程。SNode只是这个传递过程发生所需的临时协调点
5.清除: 一旦匹配完成,唤醒相关的线程,这个SNode通常会被丢弃(它的引用被移除,等待GC回收)。它不再代表任何等待的操作或存储的元素
SNode(或QNode)是协调工具,不是存储容器: 这些节点存在的唯一目的是实现生产者线程和消费者线程之间的即时、安全、高效的匹配和数据传递。它们是匹配过程的临时载体
两种策略与节点结构
SynchronousQueue 通过两种内部类实现数据传输,均继承自抽象类 Transferer
1.非公平模式(默认):TransferStack(LIFO 栈)
使用SNode
(TransferStack内部类)。栈结构(后进先出
)通常提供更高的吞吐量,但可能导致某些线程“饿死”(一直等不到配对)
结构:单向链表
(头节点为栈顶),新节点压入栈顶
匹配机制:
场景1:栈顶为互补节点(如生产者遇消费者)
直接匹配:将数据/请求传递给栈顶节点。
唤醒栈顶线程,两者同时返回
// 伪代码示例
if (栈顶为 REQUEST 且当前是 DATA) {
将当前数据传递给栈顶线程;
弹出栈顶节点;
唤醒栈顶线程;
返回;
}
超时/中断:若超时或中断,将节点移出栈
场景2:栈顶为相同类型节点
创建新节点(DATA 或 REQUEST)压入栈顶
线程阻塞等待被匹配
超时/中断:若超时或中断,将节点移出栈
2.公平模式:TransferQueue(FIFO 队列)
使用QNode
(TransferQueue内部类)。队列结构(先进先出
)保证等待时间最长的线程优先得到服务,公平性更好,但吞吐量可能略低于栈模式
结构:双向链表
(维护头尾指针),新节点插入队尾
匹配机制
场景1:队首为互补节点
直接匹配队首节点,传递数据并唤醒线程。
场景2:队首为相同类型节点
创建新节点插入队尾,线程阻塞等待。
严格FIFO:只检查队首节点,确保最先等待的线程优先匹配
put(E e): 将元素放入队列。如果没有消费者在等待,则阻塞当前线程直到有消费者取走元素。
offer(E e): 尝试立即放入元素。如果没有消费者在等待,则立即返回 false(不阻塞)。
offer(E e, long timeout, TimeUnit unit): 尝试在指定超时时间内放入元素。如果在超时前有消费者取走则返回 true,否则超时后返回 false。
take(): 取走一个元素。如果没有生产者在等待,则阻塞当前线程直到有生产者放入元素。
poll(): 尝试立即取走元素。如果没有生产者在等待,则立即返回 null(不阻塞)。
poll(long timeout, TimeUnit unit): 尝试在指定超时时间内取走元素。如果在超时前有生产者放入则返回元素,否则超时后返回 null。
//支持检查性操作 因为不存储元素 所以这些方法无意义
isEmpty(), size(), peek(), iterator(), contains(...)
1.零容量
这是它最显著的特点。SynchronousQueue 内部不存储任何元素
2.严格的配对与阻塞的直接传递:
a.插入操作(put(E e), offer(E e, long timeout, TimeUnit unit)): 一个线程(生产者)试图将一个元素放入队列时,必须等待另一个线程(消费者)来取走这个元素。如果此时没有消费者在等待取元素,生产者线程会被阻塞(或超时等待),直到有消费者到来
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
return transferer.transfer(e, true, 0) != null;
}
// 直接传递
abstract static class Transferer<E> {
abstract E transfer(E e, boolean timed, long nanos);
}
b.移除操作(take(), poll(long timeout, TimeUnit unit)): 一个线程(消费者)试图从队列中取走元素时,必须等待另一个线程(生产者)来放入一个元素。如果此时没有生产者在等待放元素,消费者线程会被阻塞(或超时等待),直到有生产者到来。
public E poll() {
return transferer.transfer(null, true, 0);
}
简单说:每一次成功的插入操作都必须与一次成功的移除操作严格配对发生
,且元素直接从生产者线程传递给消费者线程(元素直接传递不存储
)
3.两种公平策略:
a.非公平模式(默认): 使用栈(LIFO - 后进先出)管理等待线程。吞吐量通常更高,但可能导致某些线程等待时间过长(饥饿)。
b.公平模式: 使用队列(FIFO - 先进先出)管理等待线程。保证等待时间最长的线程优先获得执行机会,减少了饥饿的可能性,但吞吐量可能稍低
4.不支持检查性操作:
1).因为队列永远为空(没有存储元素),所以 isEmpty() 始终返回 true
2).size() 始终返回 0
3).iterator() 返回一个空迭代器
4).peek() 始终返回 null
优点
1.零延迟传递: 元素直接从生产者传递给消费者,没有中间存储和复制开销。
2.强制同步协调: 确保了生产者和消费者的严格同步,适用于需要精确控制执行顺序或资源交换场景
3.简化设计: 在某些特定交互模式中,使用 SynchronousQueue 比手动实现线程同步更简洁安全
4.作为 Executors.newCachedThreadPool() 的核心组件: 提供了该线程池动态伸缩和快速响应任务的特性
使用场景
1.线程池任务传递 (Executors.newCachedThreadPool()): 这是最经典的用法
newCachedThreadPool 默认使用 SynchronousQueue 作为工作队列
//SynchronousQueue 在 Executors.newCachedThreadPool() 中的使用方式
// 创建一个 cached 线程池
ExecutorService executorService = Executors.newCachedThreadPool();
// 提交一些任务到线程池
for (int i = 0; i < 10; i++) {
final int taskNumber = i;
executorService.submit(() -> {
try {
System.out.println("Task " + taskNumber +
" is running on thread " + Thread.currentThread().getName());
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 关闭线程池
executorService.shutdown();
2.高效的线程间直接通信: 当两个线程需要严格同步地交换数据或对象(一个生产,一个消费,必须手递手交接),且不希望中间有任何缓冲时。SynchronousQueue 提供了一种比 wait()/notify() 或 Lock/Condition 更高级别的抽象来实现这种精确的会合点(rendezvous)。
3.负载测试或压力测试: 可以用来模拟系统在没有任何缓冲情况下的最大吞吐量和瓶颈
缺点/注意事项
1.容易阻塞: 如果没有配对的线程(生产者找不到消费者或反之),操作会阻塞线程。必须谨慎设计线程模型,避免死锁或系统停滞。超时版本的 offer/poll 通常更安全。
2.不适用于缓冲场景: 绝对不能在需要队列缓冲(生产者生产速度 > 消费者消费速度时需要临时存储)的场景中使用它。
3.理解成本: 其行为模式与其他常见的阻塞队列差异很大,开发者需要深入理解其“零容量”和“严格配对”的特性才能正确使用。
4.公平性权衡: 需要在吞吐量(非公平)和公平性/避免饥饿(公平)之间做出选择
示例 1:基础生产者-消费者模型
public class SynchronousQueueDemo {
public static void main(String[] args) {
// 创建 SynchronousQueue(默认非公平模式)
SynchronousQueue<String> queue = new SynchronousQueue<>();
// 生产者线程
Thread producer = new Thread(() -> {
try {
String event = "数据-" + System.currentTimeMillis();
System.out.println("[" + Thread.currentThread().getName() + "] 准备发送: " + event);
// 阻塞直到消费者接收
queue.put(event);
System.out.println("[" + Thread.currentThread().getName() + "] 发送成功");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "生产者");
// 消费者线程
Thread consumer = new Thread(() -> {
try {
// 给生产者时间启动
Thread.sleep(1000);
System.out.println("[" + Thread.currentThread().getName() + "] 等待接收数据...");
// 阻塞直到生产者发送
String event = queue.take();
System.out.println("[" + Thread.currentThread().getName() + "] 接收到: " + event);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "消费者");
producer.start();
consumer.start();
}
}
输出示例:
[生产者] 准备发送: 数据-1685523057123
[消费者] 等待接收数据...
[生产者] 发送成功
[消费者] 接收到: 数据-1685523057123
示例 2:使用线程池(模拟 newCachedThreadPool)
public class ThreadPoolWithSyncQueue {
public static void main(String[] args) throws InterruptedException {
// 创建使用 SynchronousQueue 的线程池(类似 newCachedThreadPool)
ExecutorService executor = Executors.newFixedThreadPool(2);
SynchronousQueue<String> taskQueue = new SynchronousQueue<>();
// 工作线程(消费者)
executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
String task = taskQueue.take(); // 阻塞等待任务
System.out.println("执行任务: " + task);
Thread.sleep(500); // 模拟任务处理
}
});
// 提交任务(生产者)
for (int i = 1; i <= 3; i++) {
Thread.sleep(1000); // 控制任务提交速度
new Thread(() -> {
String task = "Task-" + System.nanoTime();
try {
System.out.println("提交任务: " + task);
// 如果 2 秒内没有工作线程接收,则失败
boolean offered = taskQueue.offer(task, 2, TimeUnit.SECONDS);
if (!offered) {
System.err.println("任务超时未被接收: " + task);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
}
}
输出示例:
提交任务: Task-324234523452345
执行任务: Task-324234523452345
提交任务: Task-678678678678678
执行任务: Task-678678678678678
提交任务: Task-987987987987987
执行任务: Task-987987987987987
特性 | ArrayBlocking | LinkedBlocking | PriorityBlocking | Delay | Synchronous |
---|---|---|---|---|---|
有界/无界 | 有界 | 可选有界 | 无界 | 无界 | 无容量 |
底层结构 | 数组 | 链表 | 堆(数组) | 优先级堆 | 无存储 |
排序 | FIFO | FIFO | 优先级 | 延迟时间 | FIFO/LIFO |
锁机制 | 单锁 | 双锁 | ReentrantLock | ReentrantLock | CAS/TransferQueue |
公平性 | 可选 | 无 | 无 | 无 | 可选 |
吞吐量 | 中等 | 高 | 中等 | 中等 | 非常高 |
适用场景 | 固定大小队列 | 通用队列 | 优先级处理 | 延迟任务 | 直接传递 |
场景选择
需要高吞吐 → LinkedBlockingQueue
需要无界队列 → LinkedBlockingQueue (默认)
需要公平访问 → ArrayBlockingQueue
最小化内存开销 → ArrayBlockingQueue
需要动态扩容 → 其他实现(如 ConcurrentLinkedQueue)