AQS源码解析——BlockingQueue阻塞队列篇
BlockingQueue
是Java juc包的一部分,它是一个支持阻塞操作的队列。可以实现生产者-消费者模式,在队列未空或队列已满是阻塞线程,从而实现线程之间的同步和协作。
BlockingQueue
常用实现类:
ArrayBlockingQueue
: 基于数组实现的有界阻塞队列,支持指定容量。LinkedBlockingQueue
: 基于链表实现的可选有界或无界阻塞队列。PriorityBlockingQueue
: 支持优先级的无界阻塞队列。DelayQueue
: 基于优先级的延时队列,用于定时任务调度。SynchronousQueue
: 不存储元素的阻塞队列,用于直接传输元素的场景。LinkedTransferQueue
: 基于链表实现的无界阻塞队列,支持更高的并发性能。
BlockingQueue
的一些常用方法:
put(E element)
:将元素放入队列,如果队列已满则阻塞等待。take()
:从队列中取出元素,如果队列为空则阻塞等待。offer(E element, long timeout, TimeUnit unit)
:尝试将元素放入队列,等待一段时间,如果队列仍满则返回失败。poll(long timeout, TimeUnit unit)
:尝试从队列中取出元素,等待一段时间,如果队列仍为空则返回失败。
快速上手
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class Producer implements Runnable {
private BlockingQueue<Integer> queue;
public Producer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
System.out.println("Producing: " + i);
queue.put(i); // 将数据放入队列,如果队列已满则阻塞
Thread.sleep(100); // 模拟生产时间
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
class Consumer implements Runnable {
private BlockingQueue<Integer> queue;
public Consumer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
while (true) {
int value = queue.take(); // 从队列中取出数据,如果队列为空则阻塞
System.out.println("Consuming: " + value);
Thread.sleep(300); // 模拟消费时间
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public class BlockingQueueExample {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5); // 创建容量为5的有界队列
Thread producerThread = new Thread(new Producer(queue));
Thread consumerThread = new Thread(new Consumer(queue));
producerThread.start();
consumerThread.start();
try {
// 让主线程等待一段时间
Thread.sleep(2000);
// 停止生产者和消费者线程
producerThread.interrupt();
consumerThread.interrupt();
producerThread.join();
consumerThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
工作原理
BlockingQueue
提供了阻塞操作,意味着当队列为空时,尝试获取元素的操作会被阻塞,直到有元素可用;当队列已满时,尝试放入元素的操作会被阻塞,直到有空间可用。这样,队列可以在生产者和消费者之间平衡数据流量,避免过度生产或过度消费。
如下示意案例:
+-------------+
+----> | Producer 1 |
| +-------------+
| | produce() |
| +-------------+
|
| +-------------+ +------------------+
+----> | Producer 2 | ----> | BlockingQueue | ----> (Data)
| +-------------+ +------------------+
| | produce() | | (Thread Safe) |
| +-------------+ +------------------+
|
| +-------------+
+----> | Producer 3 |
| +-------------+
| | produce() |
| +-------------+
|
| +-------------+
+----> | Consumer |
+-------------+
| consume() |
+-------------+
- 多个生产者线程(如
Producer 1
、Producer 2
、Producer 3
)通过调用produce()
方法生产数据,并将数据放入阻塞队列中。 - 一个消费者线程(如
Consumer
)通过调用consume()
方法从阻塞队列中取出数据并进行处理。 - 如果阻塞队列已满(生产者无法放入更多数据)或者为空(消费者无法取出数据),相应的操作将被阻塞,直到条件满足。
BlockingQueue
在内部维护了线程安全的机制,使得生产者和消费者线程可以在不需要显式锁的情况下实现数据的安全传递和同步。
源码分析(重点:对照流程图来看)
此处以ArrayBlockingQueue
深入AQS源码进行分析
构造函数和基本属性
//创建一个大小为5的有界队列
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
//实例化阻塞队列
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
//初始化
this.items = new Object[capacity];
//设置公平锁or非公平锁
lock = new ReentrantLock(fair);
//创建条件等待队列,notEmpty表示当阻塞队列为空时,存放生产者的等待队列
notEmpty = lock.newCondition();
//notFull表示当阻塞队列满了时,存放消费者的等待队列
notFull = lock.newCondition();
}
//队列元组,数组实现
final Object[] items;
//消费者线程从队列中取出元素时的索引
int takeIndex;
//生产者线程从队列中加入元素时的索引
int putIndex;
//队列元素个数
int count;
final ReentrantLock lock;
//notEmpty条件队列,消费者取出元素时,当队列为空时,会将线程加入该条件队列
private final Condition notEmpty;
//notFull条件队列,生产者加入元素时,当队列已满时,会将线程加入该条件队列
private final Condition notFull;
核心源码
public void put(E e) throws InterruptedException {
//非空检验
checkNotNull(e);
final ReentrantLock lock = this.lock;
//加锁 lockInterruptibly同lock相比,它会响应中断
lock.lockInterruptibly();
try {
//当items已经满了时
while (count == items.length)
//调用await,将当前线程加入到等待条件队列
notFull.await();
//入队
enqueue(e);
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
//将元素加入数组
items[putIndex] = x;
//如果队列已满
if (++putIndex == items.length)
//将put指针重置为0
putIndex = 0;
count++;
//通知notEmpty条件队列,唤醒notEmpty中等待的线程转移到CHL获锁去取出元素
notEmpty.signal();
}
重点是await()
和signal()
方法,是由Condition
提供等待唤醒机制,实现线程之间的协调和同步。类似作用于synchronized
的wait()
和notify()
方法。
public class ConditionObject implements Condition, java.io.Serializable {
//头部节点
private transient Node firstWaiter;
//尾部节点
private transient Node lastWaiter;
}
await()源码实现
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//1. 将当前线程加入到等待条件队列
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
1.将当前线程加入到等待条件队列
private Node addConditionWaiter() {
Node t = lastWaiter;
//当尾部节点不为空且ws不为CONDITION时
//当节点进入等待队列时,状态被设置为 CONDITION
//一旦条件满足,节点会被移出等待队列,并且被标记为SIGNAL状态,以便进行线程的唤醒操作。
if (t != null && t.waitStatus != Node.CONDITION) {
//清除等待队列中所有ws为SIGNAL的节点
unlinkCancelledWaiters();
t = lastWaiter;
}
//创建新等待节点node(将当前线程装配)
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//判断尾部节点为空
if (t == null)
//说明队列为空,将队头指向node
firstWaiter = node;
else
//将尾部节点后继指针指向node
t.nextWaiter = node;
//将尾部节点指向node
lastWaiter = node;
return node;
}
扫描并清除无效节点
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
//当t.ws不等于-2时,需要将当前节点从条件队列清除出去
if (t.waitStatus != Node.CONDITION) {
//将当前节点next指针设置为空
t.nextWaiter = null;
//trail表示当前队列最后的有效节点
if (trail == null)
//当前节点清除后,将next节点设置为队头
firstWaiter = next;
else
//如果trail不为空,将trail后继指针指向next
trail.nextWaiter = next;
//如果next不为空
if (next == null)
//next,则trail当之无愧为队尾
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
2.将线程加入到条件队列node后,将当前线程的锁释放掉
//释放锁并获取释放前状态,state=1
int savedState = fullyRelease(node);
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
//release 尝试去释放锁,这里加锁用的ReentrantLock,释放锁资源也由其公平锁或非公平锁来实现
//这里释放掉锁资源,会唤醒CLH队列队头线程去争抢锁资源
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
//如果释放锁失败,node节点会被标记为CANCELLED待清除状态,在后续执行中会被清除条件队列
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
3.node是否在CLH队列上
!isOnSyncQueue(node)
final boolean isOnSyncQueue(Node node) {
//如果node ws为CONDITION 或者 node前驱节点为空,说明node不在CLH队列上
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//走到这里,能够说明node ws不为CONDITION并且前驱节点不为空
//这种情况下node后继节点如果也不为空,则node一定在CLH队列上
if (node.next != null)
return true;
//next为空,则需要从CLH队尾扫描node,并发场景中,CAS会尾插多个元素进来
return findNodeFromTail(node);
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
//自旋
for (;;) {
//从队尾往前遍历,查询node是否在CLH队列上
if (t == node)
return true;
if (t == null)
//node不在队列上
return false;
t = t.prev;
}
}
4.node不在CLH而在条件队列上,则阻塞当前线程在条件队列中
while (!isOnSyncQueue(node)) {
//中断当前线程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
5. signal 唤醒及中断唤醒处理
此时会有是三种情况发生:
park
阻塞后,直到出队线程take
调用signal
方法这个时间间隔线程没有被标记中断,signal
将会node从条件等待队列转移到CLH队列,等待unpark
唤醒;park
阻塞后,在signal
方法执行前线程被标记中断,会将node转移到CLH队列,并响应中断异常park
阻塞后,在signal
方法执行后线程被标记中断,这种情况会重设中断标记,将中断处理交给开发者处理
先来看看signal
方法实现
public final void signal() {
//线程是否独占
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//等待队列头节点(第一个元素)
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
//将node从等待队列中清除
do {
//如果当前节点next为空,说明后边没有元素了
if ( (firstWaiter = first.nextWaiter) == null)
//将last指针设置为空
lastWaiter = null;
//删除next指针
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
transferForSignal
这个方法会将node节点插入到CLH上,如果返回false说明当前node被其他线程在转移,获取下一个接着转移,直到整个队列转移完成
final boolean transferForSignal(Node node) {
//将ws从CONDITION设置为0,0为入队默认状态
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//CLH 入队
Node p = enq(node);
int ws = p.waitStatus;
//如果ws<0&&设置ws=SIGNAL成功,则情况1满足,等待unpark唤醒
//如果是其他情况,则提前调用unpark唤醒线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
再来看中断处理逻辑,也就是情况2和情况3发生后的处理
private int checkInterruptWhileWaiting(Node node) {
//如果是情况1,说明线程未中断则返回0,正常获取锁
return Thread.interrupted() ?
//这个方法来处理情况2和情况2
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :0;
}
final boolean transferAfterCancelledWait(Node node) {
//能够进入这个方法说明一定是发生了中断
//如果ws设置CONDITION成功,说明是中断唤醒了park, 此时signal还没有执行
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//CLH入队
enq(node);
return true;
}
//走到这里说明,中断是发生在signal后 且在unpark唤醒前,发生了中断
//如果node不在CLH上
while (!isOnSyncQueue(node))
//signal 已经在转移了,所以等那边执行完成
Thread.yield();
return false;
}
以上就是唤醒park的三种不同情况,接着网下走
6.获锁
到这里说明线程已经被唤醒,且在CLH队列中
//获锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
//如果在获锁是发生了中断,则将interruptMode设置为REINTERRUPT
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();//上边说了,从等待队列处理ws!=CONDITION的节点。也就是情况2,从这里清除
7.设置中断
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
private void reportInterruptAfterWait(int interruptMode) throws InterruptedException{
//情况二 在signal之前中断的抛出中断异常
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
//否则设置中断
selfInterrupt();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
以上是AQS Condition
核心源码,源码很绕,需要多思考!!!