ArrayBlockingQueue
和 LinkedBlockingQueue
是 Java 并发包中常用的两种阻塞队列实现,它们都实现了 BlockingQueue
接口,提供了线程安全的队列操作。不过,它们之间存在以下几个主要区别:
-
底层实现:
ArrayBlockingQueue
基于数组实现。LinkedBlockingQueue
基于链表实现。
-
是否有界:
ArrayBlockingQueue
是有界队列,必须在创建时指定容量大小。LinkedBlockingQueue
创建时可以不指定容量大小,默认是Integer.MAX_VALUE
,也就是无界的。但也可以指定队列大小,从而成为有界的。
-
锁是否分离:
ArrayBlockingQueue
中的锁是没有分离的,即生产和消费用的是同一个锁。LinkedBlockingQueue
中的锁是分离的,即生产用的是putLock
,消费是takeLock
,这样可以防止生产者和消费者线程之间的锁争夺。
-
内存占用:
ArrayBlockingQueue
需要提前分配数组内存,创建时就会占用一定的内存空间。LinkedBlockingQueue
则是动态分配链表节点内存,根据元素的增加而逐渐占用内存空间。
详细源码解读
1. ArrayBlockingQueue 源码解读
ArrayBlockingQueue
是一个基于数组的阻塞队列。我们先来看一下它的部分源码:
java
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 void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await(); // 队列满时等待
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E e) {
final Object[] items = this.items;
items[putIndex] = e;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal(); // 通知等待的消费者
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await(); // 队列空时等待
return dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E e = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
notFull.signal(); // 通知等待的生产者
return e;
}
}
设计思路:
- 基于数组实现的有界队列,创建时必须指定容量大小,数组大小固定。
- 使用一个独占锁
ReentrantLock
保护所有的队列操作。 - 通过两个条件变量
notEmpty
和notFull
来协调生产和消费操作。
优点:
- 创建时就分配好数组内存,内存使用较为稳定。
缺点:
- 数组的长度固定,灵活性较差。
- 由于使用一个锁,生产和消费操作不能同时进行,可能存在一定的性能瓶颈。
2. LinkedBlockingQueue 源码解读
LinkedBlockingQueue
是一个基于链表的阻塞队列。我们来看一下它的部分源码:
java
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
private final int capacity; // 队列容量
private final AtomicInteger count = new AtomicInteger(0); // 元素个数
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
private 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();
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 void put(E e) throws InterruptedException {
checkNotNull(e);
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
notFull.await(); // 队列满时等待
}
enqueue(e);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal(); // 通知等待的生产者
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
private void enqueue(E e) {
last = last.next = new Node<E>(e);
}
public E take() throws InterruptedException {
int c = -1;
final ReentrantLock takeLock = this.takeLock;
final AtomicInteger count = this.count;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await(); // 队列空时等待
}
E x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal(); // 通知等待的消费者
return x;
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
}
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;
return x;
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
}
设计思路:
- 基于链表实现,默认是无界队列,但可以指定容量大小。
- 使用两个独立的锁
putLock
和takeLock
分别保护生产和消费操作。 - 通过两个条件变量
notEmpty
和notFull
来协调生产和消费操作。
优点:
- 灵活性较高,支持无界队列和有界队列。
- 由于锁分离,生产和消费操作可以并发进行,提高了性能。
缺点:
- 动态分配链表节点内存,可能会导致内存碎片。
实际应用场景
- ArrayBlockingQueue:
- 适合那些已知队列最大容量,并且希望内存使用较为稳定的场景。例如,固定大小的线程池中的任务队列。
- LinkedBlockingQueue:
- 适合那些队列大小难以预估,且对性能要求较高的场景。例如,大量数据流处理中的缓冲队列。
结论
通过对 ArrayBlockingQueue
和 LinkedBlockingQueue
的详细解读,可以看出它们在实现和应用场景上的差异。ArrayBlockingQueue
更适合容量固定,内存稳定的场景,而 LinkedBlockingQueue
则更适合对性能要求较高且容量不确定的场景