集合類源碼(四)Collection之BlockingQueue(ArrayBlockingQueue, DelayQueue, LinkedBlockingQueue)

ArrayBlockingQueue

功能

全名

public class ArrayBlockingQueue<E>
extends AbstractQueue<E>
implements BlockingQueue<E>, Serializable

簡述

由數組支持的有界阻塞隊列。這個隊列對元素FIFO(先進先出)排序。隊列的頭是隊列中存在時間最長的元素。隊列的尾部是隊列中存在時間最短的元素。新元素插入到隊列的尾部,隊列檢索操作獲取隊列頭部的元素。

這是一個典型的“有界緩衝區”,其中大小固定的數組保存由生產者插入並由消費者提取的元素。一旦創建,容量就不能更改。試圖將一個元素放入一個滿隊列將導致操作阻塞;嘗試從空隊列中獲取元素也會發生阻塞。

該類支持一個可選的公平性策略,用於對正在等待的生產者和消費者線程進行排序。默認情況下,不保證這種順序。但是,將公平性設置爲true的隊列將按FIFO順序授予線程訪問權。公平性通常會降低吞吐量,但會降低可變性並避免飢餓。

方法

// 在不超過隊列容量的情況下立即插入指定的元素,成功後返回true,如果隊列已滿則拋出IllegalStateException。
public boolean add(E e)

// 在不超過隊列容量的情況下立即在隊列末尾插入指定的元素,如果成功則返回true,如果隊列已滿則返回false。此方法通常比add(E)方法更好,後者插入元素失敗只能拋出異常。
public boolean offer(E e)

// 將指定的元素插入到此隊列的末尾,如果隊列已滿則等待直到有可用的空間。
public void put(E e) throws InterruptedException

// 將指定的元素插入到此隊列的末尾,如果隊列已滿,則在指定的超時時間之內等待空間可用,超時返回false。
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException

// 檢索並刪除此隊列的頭,如果此隊列爲空,則返回null。
public E poll()

// 檢索並刪除此隊列的頭,如有必要則等待,直到某個元素可用爲止。
public E take() throws InterruptedException

// 檢索並刪除此隊列的頭,如果有必要則在指定的等待時間之內等待元素可用,超時返回null。
public E poll(long timeout, TimeUnit unit) throws InterruptedException

// 檢索但不刪除此隊列的頭,或在此隊列爲空時返回null。
public E peek()

// 返回此隊列中的元素數量。
public int size()

// 返回此隊列在理想情況下(在沒有內存或資源約束的情況下)可以不阻塞地接受的新元素的數量。它總是等於這個隊列的初始容量減去這個隊列的當前大小。
public int remainingCapacity()

// 如果指定元素存在,則從此隊列中移除該元素的單個實例。更正式地說,如果隊列中包含一個或多個這樣的元素,則只刪除匹配到的第一個元素
public boolean remove(Object o)

// 如果此隊列包含至少一個指定的元素,則返回true。
public boolean contains(Object o)

// 返回一個數組,該數組包含此隊列中的所有元素,按適當的順序排列。返回的數組將是“安全的”,因爲此隊列不維護對它的引用。
public Object[] toArray()

// 返回一個數組,該數組包含此隊列中的所有元素,按適當的順序排列;返回數組的運行時類型是指定數組的運行時類型。
public <T> T[] toArray(T[] a)

// 返回此集合的字符串表示形式。
public String toString()

// 刪除此隊列中的所有元素。此調用返回後,隊列將爲空。
public void clear()

// 從此隊列中刪除所有可用元素並將它們添加到給定集合中。此操作可能比重複輪詢此隊列更有效。在試圖將元素添加到集合c時遇到失敗拋出相關異常時可能會導致:元素不在原集合或者集合c中,或者兩個集合中都沒有。
public int drainTo(Collection<? super E> c)

// 從該隊列中最多刪除給定數量的可用元素,並將它們添加到給定集合中。異常情況同上
public int drainTo(Collection<? super E> c, int maxElements)

// 按適當的順序返回此隊列中元素的迭代器。元素將按從第一個(head)到最後一個(tail)的順序返回。返回的迭代器是弱一致的。
public Iterator<E> iterator()

// 返回該隊列中元素的Spliterator。返回的spliterator是弱一致的。
public Spliterator<E> spliterator()

原理

/** 隊列裏元素數量 */
int count;
/** 存儲結構 */
final Object[] items;

/** 爲下一次執行 take, poll, peek or remove 操作提供的index */
int takeIndex;

/** 爲下一次執行 put, offer, or add 操作提供的index */
int putIndex;

/** 當隊列爲空時獲取等待 */
private final Condition notEmpty;

/** 當隊列滿時插入等待 */
private final Condition notFull;

offer和put

public boolean offer(E e) {
    // 不允許null值
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 隊列裏元素的數量等於隊列長度(隊列滿,插入返回false)
        if (count == items.length)
            return false;
        else {
            // 插入
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}

private void enqueue(E x) {
    // assert lock.getHoldCount() == 1;
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    // 插入數據
    items[putIndex] = x;
    // 如果putIndex+1等於隊列長度,將putIndex置爲0,爲下一輪插入做準備
    if (++putIndex == items.length)
        putIndex = 0;
    // 隊列元素數量+1
    count++;
    // 通知讀取線程可以讀取了
    notEmpty.signal();
}


public void put(E e) throws InterruptedException {
    // null值檢查
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // 獲取一個優先考慮中斷的鎖
    lock.lockInterruptibly();
    try {
        // 只要隊列滿了,則插入線程阻塞,直到有可用空間
        while (count == items.length)
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}


public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {
    // null值檢查
    checkNotNull(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 在隊列滿了之後進入循環
        while (count == items.length) {
            // 如果小於等於0則超時,返回false
            if (nanos <= 0)
                return false;
            // awaitNanos方法:使當前線程等待,直到收到signal或被中斷,或指定的等待時間過期。
            // 如果在時間之內收到了signal,則返回timeout - 已等待的時間;如果超時了,則返回0或者負數
            nanos = notFull.awaitNanos(nanos);
        }
        enqueue(e);
        return true;
    } finally {
        lock.unlock();
    }
}

poll和take

public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 如果隊列空了,直接返回null
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}

private E dequeue() {
    // assert lock.getHoldCount() == 1;
    // assert items[takeIndex] != null;
    final Object[] items = this.items;
    @SuppressWarnings("unchecked")
    E x = (E) items[takeIndex]; // 臨時變量存儲返回結果
    items[takeIndex] = null;    // 當前位置置爲空
    // 如果takeIndex+1等於隊列長度,takeIndex置爲0,爲下一輪讀取做準備
    if (++takeIndex == items.length)
        takeIndex = 0;
    // 隊列元素數量-1
    count--;
    // 如果當前迭代器不爲空,迭代器也要做出更新
    if (itrs != null)
        itrs.elementDequeued();
    // 通知寫入線程寫入數據
    notFull.signal();
    return x;
}

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 如果隊列爲空則一直等待,直到有可用元素
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    // 獲取一個優先考慮中斷的鎖
    lock.lockInterruptibly();
    try {
        // 隊列爲空進入循環
        while (count == 0) {
            // 如果小於等於0則超時,返回null
            if (nanos <= 0)
                return null;
            // awaitNanos方法:使當前線程等待,直到收到signal或被中斷,或指定的等待時間過期。
            // 如果在時間之內收到了signal,則返回timeout - 已等待的時間;如果超時了,則返回0或者負數
            nanos = notEmpty.awaitNanos(nanos);
        }
        return dequeue();
    } finally {
        lock.unlock();
    }
}

從上面的進隊出隊我們可以知道大概的流程,首先內部存儲結構是一個定長數組,初始情況putIndextakeIndex都爲0

 當執行插入後,putIndex+1,爲下一次插入做準備。當putIndex移動到最後的時候,+1正好等於隊列最大長度,這個時候要將它置爲初始狀態也就是0。takeIndex同樣的道理。

那麼爲什麼要這麼做,我已經知道了:避免了插入刪除導致的元素移動!同時也保證了隊列的性質:先進先出。

remove方法

public boolean remove(Object o) {
    if (o == null) return false;
    final Object[] items = this.items;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count > 0) {
            // final變量,不可變,下面循環做條件
            final int putIndex = this.putIndex;
            // 初始從takeIndex所指的位置開始
            int i = takeIndex;
            do {
                // 找到了,執行移除代碼
                if (o.equals(items[i])) {
                    removeAt(i);
                    return true;
                }
                // 到末尾了,從頭開始找
                if (++i == items.length)
                    i = 0;
            } while (i != putIndex);    // 只要不是putIndex所指,繼續找(因爲putIndex所指的位置是null)
        }
        return false;
    } finally {
        lock.unlock();
    }
}


void removeAt(final int removeIndex) {
    // assert lock.getHoldCount() == 1;
    // assert items[removeIndex] != null;
    // assert removeIndex >= 0 && removeIndex < items.length;
    final Object[] items = this.items;
    // 如果移除的位置和takeIndex指向一致,相當於執行了一次出隊操作,僅僅改變takeIndex即可。
    if (removeIndex == takeIndex) {
        // removing front item; just advance
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
    } else {
        // an "interior" remove

        // slide over all others up through putIndex.
        final int putIndex = this.putIndex;
        for (int i = removeIndex;;) {
            int next = i + 1;
            if (next == items.length)
                next = 0;
            // 從移除位置開始,後面的向前複製
            if (next != putIndex) {
                items[i] = items[next];
                i = next;
            } else {
                // 直到當前位置的下一個是putIndex,將當前位置置null,putIndex指向當前位置,跳出循環
                items[i] = null;
                this.putIndex = i;
                break;
            }
        }
        // 元素數量-1
        count--;
        // 同步迭代器
        if (itrs != null)
            itrs.removedAt(removeIndex);
    }
    notFull.signal();
}

remove方法,移除元素的位置不是takeIndex的時候,移除過程是下面這樣

最後看一下drainTo

// 把隊列元素刪除,並將元素添加到集合c中
public int drainTo(Collection<? super E> c) {
    // 這裏可以看到,默認刪除數量爲Integer.MAX_VALUE
    return drainTo(c, Integer.MAX_VALUE);
}
public int drainTo(Collection<? super E> c, int maxElements) {
    checkNotNull(c);
    if (c == this)
        throw new IllegalArgumentException();
    if (maxElements <= 0)
        return 0;
    final Object[] items = this.items;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 從給定的數量和隊列元素數量選出最小的一個(也就是說,無論你設置的數量爲多大,最多也就是把整個隊列清空,不然訪問不存在的位置會出異常)
        int n = Math.min(maxElements, count);
        // 從takeIndex開始
        int take = takeIndex;
        int i = 0;
        try {
            // 操作n個元素
            while (i < n) {
                @SuppressWarnings("unchecked")
                E x = (E) items[take];
                // 添加到c中
                c.add(x);
                // 原隊列刪除
                items[take] = null;
                // take向後移動;如果到了隊尾,從頭開始
                if (++take == items.length)
                    take = 0;
                i++;
            }
            return n;
        } finally {
            // Restore invariants even if c.add() threw
            if (i > 0) {
                count -= i; // 剩餘隊列元素數量
                takeIndex = take;   // 重置takeIndex
                if (itrs != null) {
                    // 如果隊列空了,迭代器也要做出同步
                    if (count == 0)
                        itrs.queueIsEmpty();
                    else if (i > take)
                        // 當takeIndex變爲0時調用。
                        itrs.takeIndexWrapped();
                }
                // 通知等待的插入線程
                for (; i > 0 && lock.hasWaiters(notFull); i--)
                    notFull.signal();
            }
        }
    } finally {
        lock.unlock();
    }
}

優缺點

優點:有界隊列,避免了內存濫用;內部插入刪除實現避免了元素移動,時間複雜度爲O(1),高效率;線程安全;

缺點:我覺得這個在特定場景已經很完美了。

如果你想在進隊出隊時不滿足條件立即返回,則直接用offer和poll;如果你希望等待,則用put和take;如果你希望等一會,不行再返回,則用offer(E e, long timeout, TimeUnit unit)和poll(long timeout, TimeUnit unit)。

remove方法儘量不用吧,因爲它在刪除之後要去判斷調整putIndex或者takeIndex。

DelayQueue

功能

全名

public class DelayQueue<E extends Delayed>
extends AbstractQueue<E>
implements BlockingQueue<E>

簡述

延遲元素的無界阻塞隊列,其中一個元素只能在其延遲過期後才能被獲取。
隊列的頭是延遲元素。如果沒有過期,就沒有head, poll將返回null。
當元素的getDelay(TimeUnit.NANOSECONDS)方法返回一個小於或等於零的值時,就會發生過期。
未過期的元素不能使用take或poll刪除,它們被視爲正常元素。

根據定義,它的元素必須是Delayed接口的實現。

方法

// 將指定的元素插入此延遲隊列。
public boolean add(E e)

// 將指定的元素插入此延遲隊列
public boolean offer(E e)

// 將指定的元素插入此延遲隊列。因爲隊列是無界的,所以這個方法永遠不會阻塞。
public void put(E e)

// 將指定的元素插入此延遲隊列。因爲隊列是無界的,所以這個方法永遠不會阻塞。
public boolean offer(E e, long timeout, TimeUnit unit)

// 檢索並刪除此隊列的頭,如果此隊列沒有過期的元素,則返回null。
public E poll()

// 檢索並刪除此隊列的頭,如有必要,將一直等待,直到此隊列上有一個過期的元素可用爲止。
public E take() throws InterruptedException

// 檢索並刪除此隊列的頭,如有必要,將一直等待,直到此隊列中具有過期延遲的元素可用,或指定的等待時間過期。
public E poll(long timeout,
              TimeUnit unit)
       throws InterruptedException

// 檢索但不刪除此隊列的頭,或在此隊列爲空時返回null。與poll方法不同,如果隊列中沒有可用的過期元素,此方法將返回下一個將要過期的元素(如果存在的話)。
public E peek()

// 返回此集合中的元素數。如果此集合元素數大於Integer.MAX_VALUE,也只返回Integer.MAX_VALUE
public int size()

// 從此隊列中刪除所有可用元素並將它們添加到給定集合中。此操作可能比重複輪詢此隊列更有效。在試圖將元素添加到集合c時遇到失敗拋出相關異常時可能會導致:元素不在原集合或者集合c中,或者兩個集合中都沒有。
public int drainTo(Collection<? super E> c)

// 從該隊列中最多刪除給定數量的可用元素,並將它們添加到給定集合中。異常情況同上
public int drainTo(Collection<? super E> c, int maxElements)

// 刪除此延遲隊列中的所有元素。此調用返回後,隊列將爲空。不等待未過期的元素;它們只是從隊列中被丟棄。
public void clear()

// 總是返回Integer.MAX_VALUE,因爲延遲隊列沒有容量限制。
public int remainingCapacity()

// 返回一個數組,該數組包含此隊列中的所有元素,按適當的順序排列。返回的數組將是“安全的”,因爲此隊列不維護對它的引用。
public Object[] toArray()

// 返回一個數組,該數組包含此隊列中的所有元素,按適當的順序排列;返回數組的運行時類型是指定數組的運行時類型。
public <T> T[] toArray(T[] a)

// 從此隊列中刪除指定元素的單個實例(如果存在),無論它是否已過期。
public boolean remove(Object o)

// 返回此隊列中所有元素(過期和未過期)的迭代器。迭代器不會以任何特定的順序返回元素。返回的迭代器是弱一致的。
public Iterator<E> iterator()

原理

成員變量

private final transient ReentrantLock lock = new ReentrantLock();
// 延遲隊列的內部存儲結構是優先級隊列     [praɪˈɒrəti][kjuː]
private final PriorityQueue<E> q = new PriorityQueue<E>();

private Thread leader = null;
// 當一個新的元素在隊列的最前面可用,或者一個新線程需要成爲leader時,就會發出條件信號。
private final Condition available = lock.newCondition();

offer和put

public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 入隊
        q.offer(e);
        if (q.peek() == e) {
            leader = null;
            // 如果隊頭元素是當前元素(新添加的元素已經過期),則通知等待線程
            available.signal();
        }
        return true;
    } finally {
        lock.unlock();
    }
}

// 實際調用的是offer
public void put(E e) {
    offer(e);
}
// 不僅僅調用的offer,並且timeout參數,unit參數都沒有用到
public boolean offer(E e, long timeout, TimeUnit unit) {
    return offer(e);
}

// java.util.PriorityQueue#offer
public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    // 內部存儲結構修改次數
    modCount++;
    int i = size;
    // 如果隊列元素數量大於等於隊列長度,則擴容
    if (i >= queue.length)
        grow(i + 1);
    // 隊列元素數量+1
    size = i + 1;
    if (i == 0)
        // 如果隊列爲空,直接放在第一位
        queue[0] = e;
    else
        // 隊列不爲空,在i的位置插入e。【這裏面有個排序的過程,根據我們定義的排序規則把最早過期的排在內部數組第一位,保證隊頭是最早過期的元素】
        siftUp(i, e);
    return true;
}
// java.util.PriorityQueue#grow
private void grow(int minCapacity) {
    int oldCapacity = queue.length;
    // 如果老容量小於64,則新容量 = 2*老容量+2;如果老容量大於等於64,則新容量 = 1.5*老容量
    int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                     (oldCapacity + 2) :
                                     (oldCapacity >> 1));
    // overflow-conscious code
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    queue = Arrays.copyOf(queue, newCapacity);
}
// java.util.PriorityQueue#peek
public E peek() {
    // 不爲空,則返回隊列第一個元素(準確來說是內部數組第一個)
    return (size == 0) ? null : (E) queue[0];
}

這裏可以看出,延遲隊列的內部實現是優先級隊列,優先級隊列的內部實現是一個數組,擴容的時候新容量與ArrayList的有一丟丟不一樣。因爲可以擴容,所以延時隊列是個無界隊列。

poll和take

public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        // 獲取隊頭
        E first = q.peek();
        // 如果隊頭爲空或者未過期,直接返回空
        if (first == null || first.getDelay(NANOSECONDS) > 0)
            return null;
        else
            // 否則出隊並返回
            return q.poll();
    } finally {
        lock.unlock();
    }
}

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            E first = q.peek();
            // 如果獲取不到,則等待
            if (first == null)
                available.await();
            else {
                // 獲取剩餘過期時間
                long delay = first.getDelay(NANOSECONDS);
                // 滿足過期條件,出隊並返回
                if (delay <= 0)
                    return q.poll();
                // 當線程等待,不保留這個引用
                first = null; // don't retain ref while waiting
                if (leader != null)
                    available.await();
                else {
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        // 等待剩餘過期時間那麼長的時間
                        available.awaitNanos(delay);
                    } finally {
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            E first = q.peek();
            // 沒有獲取到有效元素
            if (first == null) {
                // timeout無效,返回空
                if (nanos <= 0)
                    return null;
                else
                    // 否則等待timeout那麼長的時間
                    nanos = available.awaitNanos(nanos);
            } else {
                // 獲取剩餘過期時間
                long delay = first.getDelay(NANOSECONDS);
                if (delay <= 0)
                    // 已經過期,順利出隊返回
                    return q.poll();
                // timeout無效,返回空
                if (nanos <= 0)
                    return null;
                first = null; // don't retain ref while waiting
                if (nanos < delay || leader != null)
                    // 如果timeout時間小於剩餘過期時間,等待timeout那麼長的時間
                    nanos = available.awaitNanos(nanos);
                else {
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                        // 否則等待剩餘過期時間那麼長的時間
                        long timeLeft = available.awaitNanos(delay);
                        nanos -= delay - timeLeft;
                    } finally {
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
        if (leader == null && q.peek() != null)
            available.signal();
        lock.unlock();
    }
}

小例子:

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) throws Exception {
        DelayQueue<MyDelay> list = new DelayQueue<>();
        list.offer(new MyDelay(1000, "A"));
        list.offer(new MyDelay(2000, "B"));
        list.offer(new MyDelay(3000, "C"));
        for (int i = 0; i < 3; i++){
            System.out.println(list.take().toString());
        }
    }
}

class MyDelay implements Delayed {

    private long expireTime;
    private String name;

    public MyDelay(long expireTime, String name) {
        this.expireTime = System.currentTimeMillis() + expireTime;
        this.name = name;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return expireTime - System.currentTimeMillis();
    }

    @Override
    public int compareTo(Delayed o) {
        return Long.compare(expireTime, ((MyDelay)o).expireTime);
    }

    @Override
    public String toString() {
        return "MyDelay{" +
                "expireTime=" + expireTime +
                ", name='" + name + '\'' +
                '}';
    }
}

運行結果:按照過期順序輸出

優缺點

內部是依賴優先級隊列實現的,也可以說,延時隊列是優先級隊列的一個特例(按照時間過期順序排序)。因爲是無界隊列,插入不會阻塞;由於過期時間的限制,poll和take會阻塞。

使用場景:

1. 訂單超30分鐘未付款即關閉訂單。

2. 定時任務:延時隊列保存要執行的任務,一旦獲取到即執行。

3. 服務器中的客戶端連接空閒一段時間之後就需要關閉。

爲什麼用Leader-Follower模式?

官方說法:Leader爲等待隊列頭部的元素的線程。 Leader-Follower模式的這種變體可以最小化不必要的等待時間。在從take()或poll(…)返回之前,領導線程必須向其他線程發出信號,除非其他線程在此期間成爲領導線程。

模式原理:一開始創建一個線程池,選取一個當做Leader監聽任務,其它線程等待;當Leader拿到了任務,即釋放自己的權利,然後從等待線程中選取一個作爲新的Leader去監聽任務,自己則去執行拿到的任務,執行完任務進入等待狀態。由於接受任務和執行都是同一個線程,則避免了上下文切換的開銷。

LinkedBlockingQueue

功能

全名

public class LinkedBlockingQueue<E>
extends AbstractQueue<E>
implements BlockingQueue<E>, Serializable

簡述

基於鏈表的有界阻塞隊列。這個隊列對元素FIFO(先進先出)排序。隊列的頭是隊列中存在時間最長的元素。隊列的尾部是隊列中時間最短的元素。
新元素插入到隊列的尾部,隊列檢索操作獲取隊列頭部的元素。鏈表隊列通常比基於數組的隊列具有更高的吞吐量,但在大多數併發應用程序中,其性能的可預測性較差。
構造函數裏的容量參數用作防止隊列過度擴展的一種方法。容量(如果未指定)等於Integer.MAX_VALUE。

這裏可以得知,LinkedBlockingQueue的有界和無界是靈活的:如果你需要有界,則必須在構造方法指定容量;如果不指定,容量等於Integer.MAX_VALUE,和無界沒有什麼區別了。

方法

// 返回此隊列中的元素數量。
public int size()

// 返回此隊列在理想情況下(在沒有內存或資源約束的情況下)可以不阻塞地接受新元素的數量。它總是等於這個隊列的初始容量減去這個隊列的當前大小。
public int remainingCapacity()

// 將指定的元素插入到此隊列的末尾,如果需要,則等待空間可用。
public void put(E e) throws InterruptedException

// 將指定的元素插入到此隊列的末尾,如有必要,將等待指定的等待時間,直到空間可用爲止。超時則返回false。
public boolean offer(E e,
                     long timeout,
                     TimeUnit unit)
              throws InterruptedException

// 在不超過隊列容量的情況下立即在隊列末尾插入指定的元素,如果成功則返回true,如果隊列已滿則返回false。當使用容量受限的隊列時,此方法通常比add方法更好,後者插入失敗僅拋出異常。
public boolean offer(E e)

// 檢索並刪除此隊列的頭,如有必要則等待,直到某個元素可用爲止。
public E take() throws InterruptedException

// 檢索並刪除此隊列的頭,如有必要,將等待指定的等待時間,直到元素可用。超時返回null。
public E poll(long timeout, TimeUnit unit) throws InterruptedException

// 檢索並刪除此隊列的頭,如果此隊列爲空,則返回null。
public E poll()

// 檢索並刪除此隊列的頭,如果此隊列爲空,則返回null。
public E peek()

// 如果指定元素存在,則從此隊列中移除該元素的單個實例。更正式地說,如果隊列中包含一個或多個這樣的元素,則只刪除第一個匹配到的元素。
public boolean remove(Object o)

// 如果此隊列包含至少一個指定的元素,則返回true。
public boolean contains(Object o)

// 返回一個數組,該數組包含此隊列中的所有元素,按適當的順序排列。返回的數組將是“安全的”,因爲此隊列不維護對它的引用。
public Object[] toArray()

// 返回一個數組,該數組包含此隊列中的所有元素,按適當的順序排列;返回數組的運行時類型是指定數組的運行時類型。
public <T> T[] toArray(T[] a)

// 返回此集合的字符串表示形式。
public String toString()

// 刪除此隊列中的所有元素。此調用返回後,隊列將爲空。
public void clear()

// 從此隊列中刪除所有可用元素並將它們添加到給定集合中。此操作可能比重複輪詢此隊列更有效。在試圖將元素添加到集合c時遇到失敗拋出相關異常時可能會導致:元素不在原集合或者集合c中,或者兩個集合中都沒有。
public int drainTo(Collection<? super E> c)

// 從該隊列中最多刪除給定數量的可用元素,並將它們添加到給定集合中。異常情況同上
public int drainTo(Collection<? super E> c, int maxElements)

// 按適當的順序返回此隊列中元素的迭代器。元素將按從第一個(head)到最後一個(tail)的順序返回。返回的迭代器是弱一致的。
public Iterator<E> iterator()

// 返回該隊列中元素的Spliterator。返回的spliterator是弱一致的。
public Spliterator<E> spliterator()

原理

成員變量

/** 隊列容量,默認Integer.MAX_VALUE */
private final int capacity;

/** 當前隊列元素數量 */
private final AtomicInteger count = new AtomicInteger();

/**
 * 鏈表的head
 */
transient Node<E> head;

/**
 * 鏈表的tail
 */
private transient Node<E> last;

/** 由take, poll方法持有的鎖 */
private final ReentrantLock takeLock = new ReentrantLock();

/** 當take的時候,如果隊列爲空,則等待 */
private final Condition notEmpty = takeLock.newCondition();

/** 由put, offer方法持有的鎖 */
private final ReentrantLock putLock = new ReentrantLock();

/** 當put的時候,如果隊列爲空,則等待 */
private final Condition notFull = putLock.newCondition();

put和offer

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    // Note: convention in all put/take/etc is to preset local var
    // holding count negative to indicate failure unless set.
    int c = -1;
    Node<E> node = new Node<E>(e);
    // 拿到putLock
    final ReentrantLock putLock = this.putLock;
    // 拿到計數器
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        /*
         * Note that count is used in wait guard even though it is
         * not protected by lock. This works because count can
         * only decrease at this point (all other puts are shut
         * out by lock), and we (or some other waiting put) are
         * signalled if it ever changes from capacity. Similarly
         * for all other uses of count in other wait guards.
         */
        // 如果元素數量等於容量,也就是隊列滿了,則阻塞
        while (count.get() == capacity) {
            notFull.await();
        }
        // 入隊
        enqueue(node);
        // 獲取並加1;注意這裏的c = 加1之前的值
        c = count.getAndIncrement();
        // 通知其它線程繼續入隊
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    // 因爲c的值是+1之前的,所以在此情況下:實際上隊列元素數量爲1,則通知等待中的take線程來拿取
    if (c == 0)
        signalNotEmpty();
}


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 {
        // 如果隊列滿了,則等待指定時間;如果指定時間內有可用空間,則繼續向下執行;如果超時,則返回false
        while (count.get() == capacity) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        // 入隊
        enqueue(new Node<E>(e));
        // 先獲取元素數量, 然後+1
        c = count.getAndIncrement();
        // 入隊之後還有可用空間,則通知其它等待線程,繼續入隊
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return true;
}


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;
    // 注意這裏不是lockInterruptibly
    putLock.lock();
    try {
        // 如果有可用空間,則入隊;否則返回false(c= -1)
        if (count.get() < capacity) {
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
    return c >= 0;
}

take和poll

public E take() throws InterruptedException {
    E x;
    int c = -1;
    // 拿到計數器
    final AtomicInteger count = this.count;
    // 拿到takeLock
    final ReentrantLock takeLock = this.takeLock;
    // 優先響應中斷
    takeLock.lockInterruptibly();
    try {
        // 如果隊列爲空,則等待,直到有可用元素
        while (count.get() == 0) {
            notEmpty.await();
        }
        // 出隊
        x = dequeue();
        // 獲取並減1;注意這裏的c = 減1之前的值
        c = count.getAndDecrement();
        // 通知其它等待線程,繼續出隊
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    // 因爲c的值是-1之前的,所以在此情況下:隊列裏面元素數量 = 容量大小 - 1,則通知等待中的添加線程添加
    if (c == capacity)
        signalNotFull();
    return x;
}

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 {
        // 如果隊列爲空,則等待指定時間;如果指定時間內有可用元素,則繼續向下執行;如果超時,則返回null
        while (count.get() == 0) {
            if (nanos <= 0)
                return null;
            nanos = notEmpty.awaitNanos(nanos);
        }
        // 出隊
        x = dequeue();
        // 先獲取元素數量,然後-1
        c = count.getAndDecrement();
        // 通知其它等待中的take線程
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    // 通知等待中的添加線程添加
    if (c == capacity)
        signalNotFull();
    return x;
}

public E poll() {
    final AtomicInteger count = this.count;
    if (count.get() == 0)
        return null;
    E x = null;
    int c = -1;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        // 如果有可用元素,出隊;否則返回null
        if (count.get() > 0) {
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        }
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    return x;
}

peek

public E peek() {
    // 隊列爲空,返回null
    if (count.get() == 0)
        return null;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        // 返回head指向的數據
        Node<E> first = head.next;
        if (first == null)
            return null;
        else
            return first.item;
    } finally {
        takeLock.unlock();
    }
}

enqueue和dequeue

// 入隊操作,就是把新結點添加到隊列的最後
private void enqueue(Node<E> node) {
    // assert putLock.isHeldByCurrentThread();
    // assert last.next == null;
    // 尾指針的next指向新結點,當前尾指針指向尾指針的next;
    last = last.next = node;
}

// 出隊操作,就是把隊頭移除
private E dequeue() {
    // assert takeLock.isHeldByCurrentThread();
    // assert head.item == null;
    // 當前隊頭
    Node<E> h = head;
    // 下一任隊頭(head的next節點)
    Node<E> first = h.next;
    h.next = h; // help GC
    // 任命新的隊頭
    head = first;
    // 老數據
    E x = first.item;
    first.item = null;
    // 將老數據返回
    return x;
}

通過這個出隊操作,可以得知,內部的鏈表的head指針不存儲任何數據,僅僅是標記的作用(帶頭結點的鏈表,雖然多佔了一個結點的空間,但是處理邏輯變簡單了)

clear

public void clear() {
    // 拿到兩把鎖
    fullyLock();
    try {
        // 遍歷鏈表刪除
        for (Node<E> p, h = head; (p = h.next) != null; h = p) {
            h.next = h;
            p.item = null;
        }
        // 最後首尾合一
        head = last;
        // assert head.item == null && head.next == null;
        if (count.getAndSet(0) == capacity)
            notFull.signal();
    } finally {
        fullyUnlock();
    }
}

優缺點

內部結構是個鏈表,新增和刪除操作都涉及結點的創建(封裝成Node)和刪除(Node的回收),相比ArrayBlockingQueue是直接在數組位置進行數據的賦值與刪除,開銷大了一些。

相比ArrayBlockingQueue只用了一把鎖,LinkedBlockingQueue使用了takeLock和putLock兩把鎖,分別用於阻塞隊列的讀寫線程,也就是說,讀線程和寫線程可以同時運行,在多線程高併發場景,可以有更高的吞吐量。

因爲隊列的讀寫分別在頭部和尾部,相互競爭的機率較小,所以用雙鎖可以實現更高的吞吐量;而ArrayBlockingQueue,只有一個數組,也可以用雙鎖,但是代碼可就複雜了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章