阻塞队列
阻塞队列与普通队列的区别:
- 阻塞添加元素。队列已满时阻塞添加元素的线程,直到队列不满才唤醒。
- 阻塞删除元素。队列为空时阻塞取出元素的线程,直到队列不空才唤醒。
同样是阻塞队列,LinkedBlockingQueue与ArrayBlockingQueue的区别是:
LinkedBlockingQueue | ArrayBlockingQueue | |
---|---|---|
容器 | 单向链表 | 数组 |
队列容量 | 默认是Integer.MAX_VALUE ,无界队列 | 初始化必须指定大小,有界队列 |
锁 | 添加与移出用一把锁 | 添加与移出用2把锁,效率更高 |
LinkedBlockingQueue
LinkedBlockingQueue
的capacity
参数表示队列容量。默认是Integer.MAX_VALUE
,因此被称为无界队列,有OOM
的风险。
构造器方法 | 含义 |
---|---|
LinkedBlockingQueue() | 无界队列 |
LinkedBlockingQueue(int capacity) | 指定容量,有界队列 |
LinkedBlockingQueue(Collection<? extends E> c) | 将c 集合元素添加进无界队列 |
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); // head指向哑节点
}
Node<E>
节点是队列的内部类,目的是以单向链表(只有next
节点没有prev
节点)封装元素。
last
和head
是队列的首尾节点。
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();
添加元素的线程称为生产者
,取出元素的线程称为消费者
。
生产者先获取putLock
独占锁才能添加元素,消费者先获取takeLock
独占锁才能取出元素。
元素满时,生产者调用notFull.await()
,挂起自己,释放putLock
锁,直到消费者调用notFull.signal()
唤醒某个生产者,那个生产者重新获取锁后从notFull.await()
方法继续执行。
元素空时,消费者调用notEmpty .await()
,挂起自己,释放takeLock
锁,直到生产者者调用notEmpty .signal()
唤醒某个消费者,那个消费者重新获取锁后从notEmpty .await()
方法继续执行。
生产者和消费者平时是互不干扰的,直到队列为空或者队列为满时才相互交互。
添加元素
方法 | 含义 |
---|---|
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException | 阻塞特定时间将元素放入队列尾部 |
boolean offer(E e) | 非阻塞地将元素放入队列尾部 |
void put(E e) throws InterruptedException | 阻塞地将元素放入队列尾部 |
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException(); // 参数校验
long nanos = unit.toNanos(timeout);
final int c;
final ReentrantLock putLock = this.putLock; // 独占锁,确保同一时刻只有1个线程可以添加元素
final AtomicInteger count = this.count; // 当前元素数量
putLock.lockInterruptibly(); // 获取锁,可响应中断
// 运行至此,同一时刻只有当前线程可以往下执行
try {
while (count.get() == capacity) { // 队列已满
if (nanos <= 0L)
return false; // 超时,且添加元素失败,返回false
nanos = notFull.awaitNanos(nanos); // 当前线程让出锁,等待一段时间
// 运行至此,从等待状态恢复,并且重新获取`putLock`锁,继续判断`while`循环
}
enqueue(new Node<E>(e)); // 加入队列
c = count.getAndIncrement(); // getAndIncrement(),返回的是increment之前的值
if (c + 1 < capacity) // 如果队列未满,避免有生产者在等待,唤醒一个生产者
notFull.signal();
} finally {
putLock.unlock();
}
// c是当前线程获取`putLock`独占锁时的队列元素数量
// 如果c=0,代表此时队列为空,可能有很多消费者线程阻塞在`notEmpty.await()`
// 运行至此,起码当前线程往队列添加1个元素,因此唤醒生产者线程
if (c == 0)
signalNotEmpty();
return true;
}
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
// 是线程安全的,因为外层方法加锁了
private void enqueue(Node<E> node) {
// assert putLock.isHeldByCurrentThread();
// assert last.next == null;
last = last.next = node;
}
取出元素
方法 | 含义 |
---|---|
boolean remove(Object o) | 删除指定元素,判断元素相等的方法是o.equlas() 方法 |
E take() | 阻塞地取出队列首部元素 |
E poll() | 非阻塞地取出队列首部元素 |
E poll(long timeout, TimeUnit unit) throws InterruptedException | 阻塞特定时间内取出队列首部元素 |
E peek() | 查看队列首部元素 |
public boolean remove(Object o) {
if (o == null) return false;
fullyLock(); // 同时获取添加锁和取出锁
try {
for (Node<E> pred = head, p = pred.next;
p != null;
pred = p, p = p.next) { // 遍历元素删除对应节点
if (o.equals(p.item)) {
unlink(p, pred); // 从链表中删除节点
return true;
}
}
return false; // 未找到对应元素,返回false
} finally {
fullyUnlock();
}
}
// 同时获取添加锁和取出锁
void fullyLock() {
putLock.lock();
takeLock.lock();
}
// 同时释放添加锁和取出锁
void fullyUnlock() {
takeLock.unlock();
putLock.unlock();
}
// 没有 `throws InterruptedException`,不响应中断
public E poll() {
final AtomicInteger count = this.count;
if (count.get() == 0)
return null; // 非阻塞,而不是`notEmpty.await();`
final E x;
final int c;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() == 0)
return null; // 非阻塞,而不是`notEmpty.await();`
x = dequeue();
c = count.getAndDecrement();
if (c > 1) // 仍然可以取出元素, 因此唤醒下一个生产者
notEmpty.signal();
} finally {
takeLock.unlock();
}
// 与取出元素类似,c是当前线程获取`takeLock`独占锁时的队列元素数量
// 如果c=capacity,代表此时队列为满,可能有很多生产者线程无法添加元素,阻塞在`notFull.await()`
// 当前线程执行至此,队列不满,因此唤醒一个生产者线程
if (c == capacity)
signalNotFull();
return x;
}
// 取出元素
private E dequeue() {
Node<E> h = head; // head是哑结点
Node<E> first = h.next; // first是第一个存储元素的节点
h.next = h; // help GC // 让当前节点没有引用别的节点,有助于垃圾回收识别
head = first; // 舍弃当前head节点,first节点成为新的head节点
E x = first.item;
first.item = null;
return x;
}
public E peek() {
final AtomicInteger count = this.count;
if (count.get() == 0)
return null; // 如果队列没有元素,返回null
final ReentrantLock takeLock = this.takeLock;
takeLock.lock(); // 为避免线程安全问题,获取取锁
try {
return (count.get() > 0) ? head.next.item : null;
} finally {
takeLock.unlock();
}
}