LinkedBlockingQueue在并发编程中使用较多,最常见的应用场景是生产者-消费者模式中。
下面我们详细分析下实现原理,达到能手写的目的。
LinkedBlockingQueue实现了BlockingQueue接口,关于BlockingQueue几个核心方法的区别,请参考另外一篇文章《BlockingQueue(LinkedBlockingQueue/ArrayBlockingQueue)核心方法比较说明》
另外,阻塞队列的实现,还使用了Condition接口的方法,具体可参考《Java Condition接口使用Demo&原理分析》
解释一些主要的内部类和属性值。
//链表节点
static class Node<E> {
//节点元素值
E item;
//指向下一个节点的指针
Node<E> next;
//构造方法
Node(E x) { item = x; }
}
//队列的容量,一旦指定,不允许修改
private final int capacity;
//队列中节点的个数
//使用原子整形可以保证并发场景的准确性;因为插入元素和取出元素都会改变count值,存在并发
private final AtomicInteger count = new AtomicInteger();
//链表的头节点,内部元素值是null
transient Node<E> head;
//链表尾部节点,其后边不再有节点
private transient Node<E> last;
//插入元素时使用的锁
private final ReentrantLock takeLock = new ReentrantLock();
//Condition内部维护了一个等待队列,notEmpty用于存储等待 从链表中取出元素的线程节点
private final Condition notEmpty = takeLock.newCondition();
//从队列中取出元素时使用的锁
private final ReentrantLock putLock = new ReentrantLock();
//notFull用于存放等待 往链表中插入元素的线程节点
private final Condition notFull = putLock.newCondition();
此方法在其父类AbstractQueue中;
作用就是往队列中加入元素,成功立即返回true,失败立即抛出异常。
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
关键方法是offer(e),我们继续往下看。
此方法非阻塞的向队列插入元素,成功插入队列,返回true;插入失败,比如队列满了,返回false。
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
int c = -1;
//初始化一个队列节点
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
//加入队列中,直接拼接到队列尾部
enqueue(node);
//获取加一之前的值
c = count.getAndIncrement();
//元素个数小于容量,说明队列不满,发出不满通知
//注意c是加一之前的值,所以要c + 1后才是真实的队列元素个数
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
//c的值是此方法插入之前的值,如果是0,说明插入之前队列是空的,那么插入之后,肯定就不是空了,
//此时要发出非空通知,告诉等着取元素的线程,可以从队列取元素了
if (c == 0)
signalNotEmpty();
return c >= 0;
}
此方法向队列插入元素,成功插入队列,返回true;如果队列是满的,则等待一定时间;如果时间到了队列依然是满的,返回false。
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
long nanos = unit.toNanos(timeout);
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {
if (nanos <= 0)
return false;
//线程阻塞一段时间
nanos = notFull.awaitNanos(nanos);
}
enqueue(new Node<E>(e));
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return true;
}
此方法比offer(E e)就多了个notFull.awaitNanos(nanos)方法,打开看下:
//线程等待一定时间
//返回值理论上应该等于0,但是因为此方法中等待是以1000纳秒为单位,可能剩余点零头
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
//如果队列被中断,抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//加入condition等待队列,并返回此节点
Node node = addConditionWaiter();
//从AQS队列中移除,因为已经加入到了condition等待队列
int savedState = fullyRelease(node);
//停止等待的时刻
final long deadline = System.nanoTime() + nanosTimeout;
int interruptMode = 0;
//isOnSyncQueue(node):检查node节点是否在AQS的队列中
//如果不在,说明还没有被唤醒,没有资格竞争锁,进入沉睡状态
while (!isOnSyncQueue(node)) {
//如果还有剩余等待时间,则继续等待;
if (nanosTimeout <= 0L) {
//如果没有剩余等待时间,则取消等待,并将节点转移到AQS队列中
transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
//如果剩余时间大于1000纳秒,线程阻塞1000纳秒
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
//如果本节点后边有排队的,遍历他们的状态,如果等待状态被取消,则从队列中删除
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
//返回剩余等待时间
return deadline - System.nanoTime();
}
此方法,以阻塞方式向队列中插入元素,如果队列没满,则成功插入;如果队列满了,则插入线程阻塞,等待队列非满的通知。
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
//lockInterruptibly()与lock()的区别是:前者可以在等锁的过程中,响应别的线程给的中断信号,然后停止休眠状态
putLock.lockInterruptibly();
try {
//如果队列满了,则等待
//此处用while,不会重复await,因为执行一旦执行await,线程就休眠了;while是为了保证休眠后,如果被其他线程唤醒,
//需要及时再次判断队列是否已满,满了需要再次进入await,进而放置超出队列容量
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
可以看到,此方法中,使用 notFull.await();进行阻塞等待,这个方法是AQS类中的内部类ConditionObject中的方法:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
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);
}
await()方法的作用就是初始化一个节点,并加入condition的等待队列中;
注意,虽然put()方法中是while循环调await方法的,但是并不会导致重复插入等待队列。因为正常的话,while第一次循环中,就在await方法中阻塞住了,不会循环第二次。
从队列中移除某个具体的元素。成功返回true,如果此元素不存在,返回false。
public boolean remove(Object o) {
if (o == null) return false;
//同时获取put锁和take锁,就是remove操作的时候,不允许存取操作
// putLock.lock();
// takeLock.lock();
fullyLock();
try {
//遍历队列,找到就移除
for (Node<E> trail = head, p = trail.next;
p != null;
trail = p, p = p.next) {
if (o.equals(p.item)) {
unlink(p, trail);
return true;
}
}
return false;
} finally {
//释放锁
fullyUnlock();
}
}
从队列头部取出一个元素立即返回,如果队列是空的,则立即返回null
public E poll() {
final AtomicInteger count = this.count;
//count是0,说明队列是空的,直接返回null
if (count.get() == 0)
return null;
E x = null;
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() > 0) {
//从队列头部删除元素,并得到此元素
x = dequeue();
//队列基数减1
c = count.getAndDecrement();
if (c > 1)
//如果队列不是空的,通知别人
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
这个方法的作用就是删除队列的第一个元素,并返回此元素
有个疑问:为啥要假设head是空的,直接返回了第二个元素
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;假设头节点的元素是空的
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;
}
取出队列头部元素并返回;如果队列是空的,则等待一定时间;如果时间到了依然没有获取到值,返回null。
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E x = null;
int c = -1;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
if (nanos <= 0)
return null;
//线程阻塞一定时间
nanos = notEmpty.awaitNanos(nanos);
}
//从队列中删除此元素
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
//队列不是空,发送非空通知
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
//队列不满,发送不满通知
signalNotFull();
return x;
}
其中,关键部分就是比epoll()多了个等待时间方法: notEmpty.awaitNanos(nanos);
从队列头部取出元素,并返回;如果阻塞队列为空,将阻塞等待。
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
//队列是空的,则加入notEmpty condition的等待队列
notEmpty.await();
}
//从队列中删除此元素
x = dequeue();
//阻塞队列计数减一,并返回减一之前的数
c = count.getAndDecrement();
if (c > 1)
//如果队列不是空,通知因为取元素而等待的消费者
notEmpty.signal();
} finally {
takeLock.unlock();
}
//取出动作发生后,必须判断队列是否不满
//这里c是减一之前的值,此时等于队列容量,那么减一之后,肯定是不满的,发出非满通知
if (c == capacity)
signalNotFull();
return x;
}