本文继续学习另外两个并发工具类ArrayBlockingQueue和LinkedBlockingQueue。
ArrayBlockingQueue核心特性与原理
ArrayBlockingQueue是Java并发编程中一个重要的有界阻塞队列,基于数组实现,采用单个ReentrantLock控制线程安全,非常适合生产者-消费者场景。具有有界队列、线程安全、先进先出等特性。
有界队列

ArrayBlockingQueue的三个构造函数都必须传入队列容量参数,创建对比时必须指定容量,无法动态扩容。
线程安全
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/
/** Main lock guarding all access */
final ReentrantLock lock;
/** Condition for waiting takes */
@SuppressWarnings("serial") // Classes implementing Condition may be serializable.
private final Condition notEmpty;
/** Condition for waiting puts */
@SuppressWarnings("serial") // Classes implementing Condition may be serializable.
private final Condition notFull;
......
/**
* Creates an {@code ArrayBlockingQueue} with the given (fixed)
* capacity and the specified access policy.
*
* @param capacity the capacity of this queue
* @param fair if {@code true} then queue accesses for threads blocked
* on insertion or removal, are processed in FIFO order;
* if {@code false} the access order is unspecified.
* @throws IllegalArgumentException if {@code capacity < 1}
*/
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();
}
从源码可以看出,ArrayBlockingQueue采用了经典的两条件算法,通过ReentrantLock和Condition实现线程间的阻塞与唤醒,队列满时阻塞生产者,队列空时阻塞消费者。notFull条件控制生产者线程的等待和唤醒,notEmpty条件控制消费者线程的等待和唤醒。
FIFO原则
遵循先进先出的队列顺序。 FIFO 特性是通过一个循环数组来实现的,其核心在于两个指针:takeIndex 和 putIndex。
/** The queued items */
@SuppressWarnings("serial") // Conditionally serializable
final Object[] items;
/** items index for next take, poll, peek or remove */
int takeIndex;
/** items index for next put, offer, or add */
int putIndex;
/** Number of elements in the queue */
int count;
item 是存储队列元素的数组,takeIndex是下一个被取出元素的索引,putIndex是下一个被放入元素的索引,count为队列中的元素数量。
ArrayBlockingQueue核心API方法
入队方法
ArrayBlockingQueue提供了多种入队的方法,其主要区别在队列满时行为、返回值。

(1)add(E e)方法
/**
* Inserts the specified element at the tail of this queue if it is
* possible to do so immediately without exceeding the queue's capacity,
* returning {@code true} upon success and throwing an
* {@code IllegalStateException} if this queue is full.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Collection#add})
* @throws IllegalStateException if this queue is full
* @throws NullPointerException if the specified element is null
*/
public boolean add(E e) {
return super.add(e);
}
add(E e)方法往队列尾部插入一个元素,如果队列满时将抛出IllegalStateException异常,插入的元素为空则抛出NullPointerException,插入成功返回true,否则返回false,立即返回。其内部最终调用offer(E e)实现。
(2)offer(E e)方法
/**
* Inserts the specified element at the tail of this queue if it is
* possible to do so immediately without exceeding the queue's capacity,
* returning {@code true} upon success and {@code false} if this queue
* is full. This method is generally preferable to method {@link #add},
* which can fail to insert an element only by throwing an exception.
*
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
offer(E e)采用了ReentrantLock对插入元素的过程进行加锁。插入的元素为空则抛出NullPointerException,插入成功返回true,否则返回false,立即返回。该方法通常优于方法add,因为add方法在无法插入元素时只能通过抛出异常来反馈失败。
(3)offer(E e, long timeout, TimeUnit unit)方法
/**
* Inserts the specified element at the tail of this queue, waiting
* up to the specified wait time for space to become available if
* the queue is full.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
Objects.requireNonNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0L)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
offer(E e, long timeout, TimeUnit unit)方法则在队列满时等待指定的时间,直到时间消耗完后才返回,插入成功返回true,否则返回false。
(4)put(E e)方法
/**
* Inserts the specified element at the tail of this queue, waiting
* for space to become available if the queue is full.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
put(E e)方法插入时,判断队列是否已满,满了就一直阻塞等待直到能插入元素,该方法没有返回值。从上述源码可以看出,入队操作的这几个方法最终都是调用私有的enqueue(E x)方法。
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x; // 将元素放入 putIndex 位置
if (++putIndex == items.length) // putIndex 指针后移,如果到达数组末尾...
putIndex = 0; // ...则绕回数组开头(循环)
count++; // 元素数量增加
notEmpty.signal(); // 唤醒可能正在等待获取元素的消费者线程
}
出队方法
ArrayBlockingQueue同样提供了多个元素出队方法,主要区别在是否立即返回、阻塞等待、超时控制。

// 有元素立即返回,否则返回null
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
// 有元素立即返回,否则等待指定长的时间
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0L)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} 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();
}
}
这四个方法最终都是调用私有的核心出队方法dequeue()。
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex]; // 从 takeIndex 位置取出元素
items[takeIndex] = null; // 清空该位置,帮助GC
if (++takeIndex == items.length) // takeIndex 指针后移,如果到达数组末尾...
takeIndex = 0; // ...则绕回数组开头(循环)
count--; // 元素数量减少
if (itrs != null)
itrs.elementDequeued(); // 更新迭代器状态
notFull.signal(); // 唤醒可能正在等待放入元素的生产者线程
return x;
}
ArrayBlockingQueue总结
入队方法这个多个,究竟用哪个?通过上面的分析,不难发现,add()方法在队列满时抛出异常,对于日常的排队需求,很明显不符合要求;offer()方法相对add()方法友好一点,通过返回true或false表示插入结果,但是不会等待;通常很多排队场景是需要等待的,一直等待直到队列能插入元素为止,所以put()方法最符合日常需求,这也是为什么叫阻塞队列原因。
| 方法 | 队列满时行为 | 返回值 |
|---|---|---|
add(E e) | 抛出IllegalStateException | boolean |
offer(E e) | 立即返回false | boolean |
put(E e) | 阻塞直到队列有空位 | void |
offer(E e, long timeout, TimeUnit unit) | 阻塞指定时间后返回false | boolean |
同理,出队方法中具有阻塞特性的是take()方法,取元素时等待直到有元素可以取为止。
| 方法 | 队列空时行为 | 返回值 |
|---|---|---|
poll() | 立即返回null | E |
take() | 阻塞直到队列有元素 | E |
poll(long timeout, TimeUnit unit) | 阻塞指定时间后返回null | E |
LinkedBlockingQueue核心特性与原理
LinkedBlockingQueue 是 Java 并发包中基于链表的阻塞队列实现,同样采用生产者-消费者模式,是处理多线程数据交换的重要工具。具有链表结构、容量灵活、线程安全与双锁设计、生产者-消费者模型等特性。
底层数据结构
/**
* Linked list node class.
*/
static class Node<E> {
E item;
/**
* One of:
* - the real successor Node
* - this Node, meaning the successor is head.next
* - null, meaning there is no successor (this is the last node)
*/
Node<E> next;
Node(E x) { item = x; }
}
/** The capacity bound, or Integer.MAX_VALUE if none */
private final int capacity;
/** Current number of elements */
private final AtomicInteger count = new AtomicInteger();
/**
* Head of linked list.
* Invariant: head.item == null
*/
transient Node<E> head;
/**
* Tail of linked list.
* Invariant: last.next == null
*/
private transient Node<E> last;
Node类是单向链表的节点,存储队列元素;Node类型的head 和 last节点 分别指向链表首尾
原子计数器count 记录当前队列元素个数。
容量灵活
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
/**
* Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
*
* @param capacity the capacity of this queue
* @throws IllegalArgumentException if {@code capacity} is not greater
* than zero
*/
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
创建LinkedBlockingQueue时,可以指定容量,不指定容量时默认为 Integer.MAX_VALUE,可视为无界队列。
线程安全与双锁设计
/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
@SuppressWarnings("serial") // Classes implementing Condition may be serializable.
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
@SuppressWarnings("serial") // Classes implementing Condition may be serializable.
private final Condition notFull = putLock.newCondition();
takeLock是取元素用的锁,putLock是插入元素用的锁。notEmpty:队列为空时,出队线程在此条件变量上等待;notFull:队列满时,入队线程在此条件变量上等待。
核心API方法
入队方法
和ArrayBlockingQueue一样,LinkedBlockingQueue同样提供了add/offer/put这几个方法,功能同ArrayBlockingQueue。我们截取其中的阻塞方法分析下:
/**
* 将指定元素插入此队列的尾部,必要时等待空间可用。
* 这是阻塞队列的核心方法之一,当队列满时会阻塞当前线程。
*
* @throws InterruptedException 如果在线程等待时被中断
* @throws NullPointerException 如果指定的元素为 null
*/
public void put(E e) throws InterruptedException {
// 参数校验:不允许插入 null 元素
if (e == null) throw new NullPointerException();
// 定义局部变量 c,用于记录插入前的队列元素数量
final int c;
// 创建新的节点包装要插入的元素
final Node<E> node = new Node<E>(e);
// 获取入队锁和原子计数器的本地引用,避免后续访问 this 指针
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
// 获取入队锁,支持中断响应
putLock.lockInterruptibly();
try {
/*
* 注意:这里使用 count 作为等待条件判断,即使它没有被当前锁保护。
* 这样是可行的,因为在持有 putLock 期间,count 只能减少(其他入队操作被锁阻塞),
* 当 count 从容量值改变时,我们(或其他等待的入队线程)会被通知唤醒。
* 同样适用于其他等待条件中使用 count 的情况。
*/
// 循环检查队列是否已满(使用循环是为了防止虚假唤醒)
while (count.get() == capacity) {
// 队列满,当前线程在 notFull 条件上等待
notFull.await();
}
// 队列有空闲空间,将新节点加入链表尾部
enqueue(node);
// 原子性地增加计数器,并返回增加前的值
c = count.getAndIncrement();
// 如果插入后队列仍未满,唤醒其他可能正在等待的入队线程
if (c + 1 < capacity)
notFull.signal();
} finally {
// 无论是否成功,最终都要释放锁
putLock.unlock();
}
// 如果插入前队列为空(c == 0),需要唤醒可能正在等待的消费线程
if (c == 0)
signalNotEmpty();
}
/**
* 将节点添加到队列尾部。
* 该方法由持有putLock的生产者线程调用,确保线程安全。
* 通过修改last指针实现O(1)时间复杂度操作。
*
* @param node 待添加的节点
*/
private void enqueue(Node<E> node) {
// 调试断言:当前线程必须持有putLock
// assert putLock.isHeldByCurrentThread();
// 调试断言:队列原为空(last.next == null)
// assert last.next == null;
// 原子操作:将新节点链接到队列尾部
last = last.next = node;
}
出队方法
和ArrayBlockingQueue一样,LinkedBlockingQueue同样提供了poll/remove/take这几个方法,功能同ArrayBlockingQueue。我们截取其中的阻塞方法分析下:
public E take() throws InterruptedException {
// 定义局部变量:x用于存储出队元素,c用于记录出队前的队列元素数量
final E x;
final int c;
// 获取原子计数器和出队锁的本地引用,优化性能
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
// 获取出队锁,支持中断响应
takeLock.lockInterruptibly();
try {
// 循环检查队列是否为空(使用循环防止虚假唤醒)
while (count.get() == 0) {
// 队列为空,当前线程在notEmpty条件上等待
notEmpty.await();
}
// 队列非空,从链表头部移除元素
x = dequeue();
// 原子性地减少计数器,并返回减少前的值
c = count.getAndDecrement();
// 如果出队前队列中还有多于1个元素,唤醒其他可能正在等待的消费线程
if (c > 1)
notEmpty.signal();
} finally {
// 确保锁被释放,避免死锁
takeLock.unlock();
}
// 如果出队前队列是满的(c == capacity),需要唤醒可能正在等待的生产线程
if (c == capacity)
signalNotFull();
// 返回出队的元素
return x;
}
ArrayBlockingQueue与LinkedBlockingQueue性能对比
ArrayBlockingQueue和LinkedBlockingQueue在并发性能上的差异主要源于它们的锁机制设计,这直接影响了生产者和消费者能否同时工作。
ArrayBlockingQueue 使用单锁设计,生产者和消费者共用一把锁。这意味着在任何时刻,只能有一个线程执行入队或出队操作,两者无法并行。
LinkedBlockingQueue 采用双锁设计,拥有独立的入队锁(putLock)和出队锁(takeLock)。这使得生产者和消费者可以真正并发执行,大幅提升吞吐量。
下面通过一个例子来对比两者的并发性能:创建相同数量的生产者和消费者线程,分别使用ArrayBlockingQueue和LinkedBlockingQueue,统计相同时间内的任务处理数量。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
public class QueueBenchmark {
private static final int PRODUCER_COUNT = 4;
private static final int CONSUMER_COUNT = 4;
private static final int TEST_DURATION_SECONDS = 10;
private static final int QUEUE_CAPACITY = 1000;
private static class Producer implements Runnable {
private final BlockingQueue<Integer> queue;
private final AtomicLong counter;
private volatile boolean running = true;
public Producer(BlockingQueue<Integer> queue, AtomicLong counter) {
this.queue = queue;
this.counter = counter;
}
@Override
public void run() {
try {
while (running) {
queue.put(1);
counter.incrementAndGet();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void stop() {
running = false;
}
}
private static class Consumer implements Runnable {
private final BlockingQueue<Integer> queue;
private final AtomicLong counter;
private volatile boolean running = true;
public Consumer(BlockingQueue<Integer> queue, AtomicLong counter) {
this.queue = queue;
this.counter = counter;
}
@Override
public void run() {
try {
while (running) {
queue.take();
counter.incrementAndGet();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void stop() {
running = false;
}
}
public static long benchmarkQueue(BlockingQueue<Integer> queue, String queueType)
throws InterruptedException {
AtomicLong produceCounter = new AtomicLong(0);
AtomicLong consumeCounter = new AtomicLong(0);
Producer[] producers = new Producer[PRODUCER_COUNT];
Consumer[] consumers = new Consumer[CONSUMER_COUNT];
Thread[] producerThreads = new Thread[PRODUCER_COUNT];
Thread[] consumerThreads = new Thread[CONSUMER_COUNT];
// 创建并启动生产者线程
for (int i = 0; i < PRODUCER_COUNT; i++) {
producers[i] = new Producer(queue, produceCounter);
producerThreads[i] = new Thread(producers[i], "Producer-" + queueType + "-" + i);
producerThreads[i].start();
}
// 创建并启动消费者线程
for (int i = 0; i < CONSUMER_COUNT; i++) {
consumers[i] = new Consumer(queue, consumeCounter);
consumerThreads[i] = new Thread(consumers[i], "Consumer-" + queueType + "-" + i);
consumerThreads[i].start();
}
// 运行测试指定时间
Thread.sleep(TEST_DURATION_SECONDS * 1000);
// 停止所有线程
for (Producer producer : producers) {
producer.stop();
}
for (Consumer consumer : consumers) {
consumer.stop();
}
// 中断线程以确保快速停止
for (Thread thread : producerThreads) {
thread.interrupt();
}
for (Thread thread : consumerThreads) {
thread.interrupt();
}
// 等待所有线程结束
for (Thread thread : producerThreads) {
thread.join(1000);
}
for (Thread thread : consumerThreads) {
thread.join(1000);
}
long totalOperations = produceCounter.get() + consumeCounter.get();
System.out.printf("%s 性能结果:%n", queueType);
System.out.printf(" 生产操作数: %,d%n", produceCounter.get());
System.out.printf(" 消费操作数: %,d%n", consumeCounter.get());
System.out.printf(" 总操作数: %,d%n", totalOperations);
System.out.printf(" 吞吐量: %,d 操作/秒%n", totalOperations / TEST_DURATION_SECONDS);
System.out.println();
return totalOperations;
}
public static void main(String[] args) throws InterruptedException {
System.out.println("开始队列性能对比测试...");
System.out.printf("测试配置: %d 生产者, %d 消费者, %d 秒测试时间, 队列容量 %d%n%n",
PRODUCER_COUNT, CONSUMER_COUNT, TEST_DURATION_SECONDS, QUEUE_CAPACITY);
// 测试 ArrayBlockingQueue
BlockingQueue<Integer> arrayQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
long arrayOps = benchmarkQueue(arrayQueue, "ArrayBlockingQueue");
// 测试 LinkedBlockingQueue
BlockingQueue<Integer> linkedQueue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
long linkedOps = benchmarkQueue(linkedQueue, "LinkedBlockingQueue");
// 性能对比分析
double performanceRatio = (double) linkedOps / arrayOps;
System.out.println("性能对比分析:");
System.out.printf("LinkedBlockingQueue 比 ArrayBlockingQueue 快 %.2f 倍%n", performanceRatio);
if (performanceRatio > 1.0) {
System.out.println("结论: LinkedBlockingQueue 在并发场景下展现出明显的吞吐量优势");
} else {
System.out.println("结论: 在当前测试条件下,两种队列性能相近");
}
}
}
运行结果:
开始队列性能对比测试...
测试配置: 4 生产者, 4 消费者, 10 秒测试时间, 队列容量 1000
ArrayBlockingQueue 性能结果:
生产操作数: 104,607,461
消费操作数: 104,607,461
总操作数: 209,214,922
吞吐量: 20,921,492 操作/秒
LinkedBlockingQueue 性能结果:
生产操作数: 60,179,525
消费操作数: 60,179,524
总操作数: 120,359,049
吞吐量: 12,035,904 操作/秒
性能对比分析:
LinkedBlockingQueue 比 ArrayBlockingQueue 快 0.58 倍
结论: 在当前测试条件下,两种队列性能相近
从运行结果看,线程数量比较少的情况下,ArrayBlockingQueue的性能反而比LinkedBlockingQueue的好。加大生产消费线程的数量到10,再次运行程序:
开始队列性能对比测试...
测试配置: 10 生产者, 10 消费者, 10 秒测试时间, 队列容量 1000
ArrayBlockingQueue 性能结果:
生产操作数: 56,185,094
消费操作数: 56,185,094
总操作数: 112,370,188
吞吐量: 11,237,018 操作/秒
LinkedBlockingQueue 性能结果:
生产操作数: 56,596,683
消费操作数: 56,595,693
总操作数: 113,192,376
吞吐量: 11,319,237 操作/秒
性能对比分析:
LinkedBlockingQueue 比 ArrayBlockingQueue 快 1.01 倍
结论: LinkedBlockingQueue 在并发场景下展现出明显的吞吐量优势
加大生产消费线程的数量到20,再次运行程序:
开始队列性能对比测试...
测试配置: 20 生产者, 20 消费者, 10 秒测试时间, 队列容量 1000
ArrayBlockingQueue 性能结果:
生产操作数: 33,684,938
消费操作数: 33,684,938
总操作数: 67,369,876
吞吐量: 6,736,987 操作/秒
LinkedBlockingQueue 性能结果:
生产操作数: 57,188,358
消费操作数: 57,187,361
总操作数: 114,375,719
吞吐量: 11,437,571 操作/秒
性能对比分析:
LinkedBlockingQueue 比 ArrayBlockingQueue 快 1.70 倍
结论: LinkedBlockingQueue 在并发场景下展现出明显的吞吐量优势
从运行结果可以看出,随着并发线程数的增多,LinkedBlockingQueue的性能优势越明显。
ArrayBlockingQueue与LinkedBlockingQueue底层解析
691

被折叠的 条评论
为什么被折叠?



