文章目錄
1 簡介
LinkedBlockingQueue在併發編程中使用較多,最常見的應用場景是生產者-消費者模式中。
下面我們詳細分析下實現原理,達到能手寫的目的。
LinkedBlockingQueue實現了BlockingQueue接口,關於BlockingQueue幾個核心方法的區別,請參考另外一篇文章《BlockingQueue(LinkedBlockingQueue/ArrayBlockingQueue)核心方法比較說明》
另外,阻塞隊列的實現,還使用了Condition接口的方法,具體可參考《Java Condition接口使用Demo&原理分析》
2 類的構成
解釋一些主要的內部類和屬性值。
//鏈表節點
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();
3 核心方法
3.1加入方法
3.1.1 add(E e)
此方法在其父類AbstractQueue中;
作用就是往隊列中加入元素,成功立即返回true,失敗立即拋出異常。
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
關鍵方法是offer(e),我們繼續往下看。
3.1.2 offer(E 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;
}
3.1.3 offer(E e, long timeout, TimeUnit unit)
此方法向隊列插入元素,成功插入隊列,返回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();
}
3.1.4 put(E e)
此方法,以阻塞方式向隊列中插入元素,如果隊列沒滿,則成功插入;如果隊列滿了,則插入線程阻塞,等待隊列非滿的通知。
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方法中阻塞住了,不會循環第二次。
3.2 拿出方法
3.2.1 remove(Object)
從隊列中移除某個具體的元素。成功返回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();
}
}
3.2.2 poll()
從隊列頭部取出一個元素立即返回,如果隊列是空的,則立即返回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;
}
3.2.3 poll(long , TimeUnit)
取出隊列頭部元素並返回;如果隊列是空的,則等待一定時間;如果時間到了依然沒有獲取到值,返回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);
3.2.4 take()
從隊列頭部取出元素,並返回;如果阻塞隊列爲空,將阻塞等待。
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;
}