阻塞隊列LinkedBlockingQueue原理和源碼解析

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章