Java同步互斥访问二(AQS框架)--> BlockingQueue
1、BlockingQueue简介
1.1、什么是BlockingQueue
BlockingQueue,也叫阻塞队列,它是线程通信的一个工具,在任意时刻,不管并发有多高,在单JVM上,同一时间永远只有一个线程能够对队列进行入队或者出队操作,类似于生产者、消费者模式。
1.2、BlockingQueue的api
添加元素
方法 | 说明 |
---|---|
add() | 如果插入成功则返回 true,否则抛出 IllegalStateException 异常 |
put() | 将指定的元素插入队列,如果队列满了,那么会阻塞直到有空间插入 |
offer() | 如果插入成功则返回 true,否则返回 false |
offer(E e, long timeout, TimeUnit unit) | 尝试将元素插入队列,如果队列已满,那么会阻塞直到有空间插入,但是会有等待超时时间 |
检索元素
方法 | 说明 |
---|---|
take() | 获取队列的头部元素并将其删除,如果队列为空,则阻塞并等待元素变为可用 |
poll(long timeout, TimeUnit unit) | 检索并删除队列的头部,如有必要,等待指定的等待时间以使元素可用,如果超时,则返回 null |
2、源码(ArrayBlockingQueue)
2.1、构造方法
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
// 点击上面代码的this即可进入到下面的代码
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
// 创建一把锁,这个锁后续在put元素等时候用
lock = new ReentrantLock(fair);
// 条件对象,在take方法时使用
notEmpty = lock.newCondition();
// 条件对象,在put方法时使用
notFull = lock.newCondition();
}
2.2、put方法
先加锁,加上锁后,如果阻塞队列有空间,则继续生产,如果没有空间,则将生产者放到条件等待队列进行阻塞,直到有空间了之后便可以唤醒继续放入元素了,放完元素之后就得把锁释放掉了。
先放一张图
public void put(E e) throws InterruptedException {
checkNotNull(e);
// 定义一个锁
final ReentrantLock lock = this.lock;
// 去加锁
lock.lockInterruptibly();
try {
// 这里判断队列是否被放满
while (count == items.length)
// 队列被放满了,进入条件等待队列阻塞,走2.2.1
notFull.await();
// 队列没有被放满,直接入队,并释放一个signal信号,走2.2.3
enqueue(e);
} finally {
// 入队完成,释放锁资源,使得消费者可以获取到
lock.unlock();
}
}
2.2.1、notFull.await()
await方法会组成一个条件等待队列,将生产者的放到队列中,逐个释放锁的资源
public final void await() throws InterruptedException {
// 判断当前线程是否被中断,如果是,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 组建一个条件等待队列,目的是将生产者放进来,因为现在已经满了,不能再生产了,走2.2.2
Node node = addConditionWaiter();
// 这里去释放锁,因为马上要进行阻塞,先释放掉锁,使得消费者可以去访问资源
int savedState = fullyRelease(node);
int interruptMode = 0;
// 下面这段代码的大概逻辑就是:
// 将生产者全部放到条件等待队列中并park住,然后唤醒同步队列的第一个节点(也就是消费者)
// 让它去消费,然后消费了之后会发一个signal信号给生产者,让他重新获取锁去生产数据
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);
}
2.2.2、addConditionWaiter()
这个方法是组成条件队列的方法:
private Node addConditionWaiter() {
Node t = lastWaiter;
// 这里就是去判断是否有无效的节点,如果有,就把它清除掉
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 将当前节点包装成一个条件节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 将头 firstWaiter 指向头节点,将 lastWaiter 指向尾节点
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
2.2.3、enqueue(E x)
入队,入队成功之后,给生产者发送一个signal信号。
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
// 去发信号,并作一系列操作
notEmpty.signal();
}
2.3、take()
取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻断进入等待状态直到 Blocking 有新的对象被加入为止。
先放一张图
public E take() throws InterruptedException {
// 定义一个锁
final ReentrantLock lock = this.lock;
// 加锁
lock.lockInterruptibly();
try {
// 判断队列是否为空
while (count == 0)
// 如果队列为空,走2.2.1
notEmpty.await();
// 出队,在出队的时候会向生产者发送一个signal信号
// 如果生产者在条件等待队列中,则把它挪到同步队列中,继续生产资源,走2.3.3
return dequeue();
} finally {
// 释放锁
lock.unlock();
}
}
2.2.1、notEmpty.await()
await方法会组成一个条件等待队列,将消费者的放到队列中,逐个释放锁的资源,其实和2.1.1的await方法基本一致,只不过这次的条件从生产者变成了消费者。
public final void await() throws InterruptedException {
// 判断当前线程是否被中断,如果是,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 组建一个条件等待队列,目的是将消费者放进来,因为队列已经为空了,没有资源可以消费了,走2.3.2
Node node = addConditionWaiter();
// 这里去释放锁,因为马上要进行阻塞,先释放掉锁,使得生产者可以获取锁并生产资源
int savedState = fullyRelease(node);
int interruptMode = 0;
// 下面这段代码的大概逻辑就是:
// 将消费者全部放到条件等待队列中并park住,然后唤醒同步队列的第一个节点(也就是生产者)
// 让它去生产,然后生产了之后会发一个signal信号给消费者,让他重新获取锁去消费数据
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);
}
2.3.2、addConditionWaiter()
这个方法是组成条件队列的方法:
private Node addConditionWaiter() {
Node t = lastWaiter;
// 这里就是去判断是否有无效的节点,如果有,就把它清除掉
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
// 将当前节点包装成一个条件节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
// 将头 firstWaiter 指向头节点,将 lastWaiter 指向尾节点
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
2.3.3、dequeue()
如果生产者在条件等待队列中,则把它挪到同步队列中,继续生产资源
出队,在出队的时候会向生产者发送一个signal信号
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
// 出队,在出队的时候会向生产者发送一个signal信号
notFull.signal();
return x;
}