【JDK原始碼分析系列】ArrayBlockingQueue原始碼分析

奮鬥企鵝CopperSun發表於2020-09-27

【JDK原始碼分析系列】ArrayBlockingQueue原始碼分析

【1】ArrayBlockingQueue 繼承體系圖示

【2】ArrayBlockingQueue 原始碼分析

【2.1】ArrayBlockingQueue 主要屬性

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    // 1. 利用陣列儲存元素
    // 2. 通過放指標和取指標來標記下一次操作的位置
    // 3. 利用重入鎖來保證併發安全
    //
    // 佇列存放在 Object 的陣列裡面
    // 陣列大小必須在初始化的時候手動設定,沒有預設大小
    final Object[] items;
    // 下次拿資料的時候的索引位置
    int takeIndex;
    // 下次放資料的索引位置
    int putIndex;
    // 當前已有元素的數量
    int count;
    // 可重入的鎖,保證併發訪問
    final ReentrantLock lock;
    // 非空條件
    private final Condition notEmpty;
    // 非滿條件
    private final Condition notFull;
}

【2.2】ArrayBlockingQueue 構造方法

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    // 1. 有界的阻塞陣列,容量一旦建立,後續大小無法修改
    // 2. 元素是有順序的,按照先入先出進行排序,從隊尾插入資料資料,從隊頭拿資料
    // 3. 佇列滿時,往佇列中 put 資料會被阻塞,佇列空時,往佇列中拿資料也會被阻塞
    //
    // 公平和非公平指的是讀寫鎖的,比如說現在佇列是滿的,還有很多執行緒執行
    // put 操作,必然會有很多執行緒等待,在佇列不滿時,會喚醒等待的執行緒
    // fair 如果是 true 話,就會按照執行緒等待的排隊順序喚醒執行緒
    // 如果是 false 的話,就會隨機喚醒執行緒
    // 通過利用鎖的公平和非公平,來實現了 put 和 take 阻塞被喚醒時的公平和非公平
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        // 初始化陣列,初始化時必須傳入容量即陣列的大小
        this.items = new Object[capacity];
        // 建立重入鎖及兩個條件
        lock = new ReentrantLock(fair);
        // 佇列不為空 Condition,在 put 成功時使用
        notEmpty = lock.newCondition();
        // 佇列不滿 Condition,在 take 成功時使用
        notFull =  lock.newCondition();
    }

    public ArrayBlockingQueue(int capacity, boolean fair,
                              Collection<? extends E> c) {
        this(capacity, fair);

        final ReentrantLock lock = this.lock;
        lock.lock(); // Lock only for visibility, not mutual exclusion
        try {
            // i 代表插入的位置
            int i = 0;
            try {
                // 注意,如果 c 的大小超過了陣列的大小會拋異常
                for (E e : c) {
                    // 元素不能為null
                    checkNotNull(e);
                    items[i++] = e;
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                throw new IllegalArgumentException();
            }
            // 更新已有資料數量
            count = i;
            // 如果插入的位置,正好是隊尾了,下次需要從 0 開始插入
            putIndex = (i == capacity) ? 0 : i;
        } finally {
            lock.unlock();
        }
    }
}

【2.3】ArrayBlockingQueue 新增元素

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    public boolean add(E e) {
        // 呼叫父類AbstractQueue的add方法
        // 在父類AbstractQueue的add方法中實際呼叫了offer方法
        // offer方法由子類覆寫
        return super.add(e);
    }

    // 新增
    public boolean offer(E e) {
        // 判斷元素是否為null
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            // 如果佇列是滿的,直接返回false
            if (count == items.length)
                return false;
            else {
                // 新增
                // 如果陣列沒滿就呼叫入隊方法並返回true
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {
        // 判斷元素是否為null
        checkNotNull(e);
        // 獲取timeout值
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        // 加鎖,如果執行緒中斷了丟擲異常
        lock.lockInterruptibly();
        try {
            // 如果陣列滿了,就阻塞nanos納秒
            // 如果喚醒該執行緒時依然沒有空間且時間到了就返回false
            while (count == items.length) {
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);
            }
            // 元素入隊
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }

    // 新增,如果佇列滿,無限阻塞
    public void put(E e) throws InterruptedException {
        // 元素不能為空
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        // 加鎖,如果執行緒中斷了丟擲異常
        lock.lockInterruptibly();
        try {
            // 佇列如果是滿的,就無限等待
            // 一直等待佇列中有資料被拿走時才會被喚醒
            while (count == items.length)
                notFull.await();
            // 元素入隊
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

    private void enqueue(E x) {
        // 獲取元素陣列
        final Object[] items = this.items;
        // putIndex 為本次插入的位置
        items[putIndex] = x;
        // ++ putIndex 計算下次插入的位置
        // 如果下次插入的位置,正好等於隊尾,下次插入就從 0 開始
        if (++putIndex == items.length)
            putIndex = 0;
        // 佇列中已有元素數量增1
        count++;
        // 喚醒notEmpty,因為入隊了一個元素,所以肯定不為空
        notEmpty.signal();
    }
}

【2.4】ArrayBlockingQueue 獲取元素

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        // 加鎖,如果執行緒中斷了丟擲異常
        lock.lockInterruptibly();
        try {
            // 如果佇列為空,無限等待
            // 直到佇列中有資料被 put 後被喚醒
            // 如果佇列無元素,則阻塞等待在條件notEmpty上
            while (count == 0)
                notEmpty.await();
            // 從佇列中拿資料
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    public E poll() {
        final ReentrantLock lock = this.lock;
        // 加鎖
        lock.lock();
        try {
            // 如果佇列沒有元素則返回null,否則出隊
            return (count == 0) ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        // 獲取timeout
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        // 加鎖,如果執行緒中斷了丟擲異常
        lock.lockInterruptibly();
        try {
            // 如果佇列無元素,則阻塞等待nanos納秒
            // 如果下一次這個執行緒獲得了鎖但佇列依然無元素且已超時就返回null
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            // 做出隊處理
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    private E dequeue() {
        // 獲取陣列
        final Object[] items = this.items;
        // 獲取取索引處的元素
        // takeIndex 代表本次拿資料的位置,是上一次拿資料時計算好的
        E x = (E) items[takeIndex];
        // 把取指標位置設為null
        // 幫助 gc
        items[takeIndex] = null;
        // ++ takeIndex 計算下次拿資料的位置
        // 如果正好等於隊尾的話,下次就從 0 開始拿資料
        if (++takeIndex == items.length)
            takeIndex = 0;
        // 佇列實際大小減 1
        count--;
        if (itrs != null)
            // 與迭代器相關的操作
            itrs.elementDequeued();
        // 喚醒notFull條件
        // 喚醒被佇列滿阻塞的執行緒
        notFull.signal();
        return x;
    }
}

【2.5】ArrayBlockingQueue 刪除元素

情況一,takeIndex == removeIndex

情況二, takeIndex != removeIndex

1). removeIndex + 1 != putIndex

2). removeIndex + 1 == putIndex

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    // 情況1. 刪除位置和 takeIndex 一樣
    // 比如 takeIndex 是 2,而要刪除的位置正好也是 2,則將位置 2 的資料置為 null ,並重新計算 takeIndex 為 3
    //
    // 情況2. 刪除位置和 takeIndex 不一樣
    // 找到要刪除元素的下一個並比較其和 putIndex 的關係
    // 如果下一個元素不是 putIndex,就把下一個元素往前移動一位
    // 如果下一個元素是 putIndex,把 putIndex 的值修改成刪除的位置
    void removeAt(final int removeIndex) {
        // 獲取陣列
        final Object[] items = this.items;
        // 情況1,如果刪除位置正好等於下次要拿資料的位置
        // 即removeIndex == takeIndex
        if (removeIndex == takeIndex) {
            // 下次要拿資料的位置直接置空
            items[takeIndex] = null;
            // 要拿資料的位置往後移動一位
            if (++takeIndex == items.length)
                takeIndex = 0;
            count--;
            // 處理迭代器相關的邏輯
            if (itrs != null)
                itrs.elementDequeued();
        // 情況 2,如果刪除位置不是下次要拿資料的位置
        // 即removeIndex != takeIndex
        } else {
            // 獲取下次放入資料的位置
            final int putIndex = this.putIndex;
            for (int i = removeIndex;;) {
                // 找到要刪除元素的下一個
                int next = i + 1;
                if (next == items.length)
                    next = 0;
                // 下一個元素不是 putIndex
                if (next != putIndex) {
                    // 下一個元素往前移動一位
                    items[i] = items[next];
                    i = next;
                // 下一個元素是 putIndex
                } else {
                    // 刪除元素
                    items[i] = null;
                    // 下次放元素時,應該從本次刪除索引處放置
                    this.putIndex = i;
                    break;
                }
            }
            // 陣列中已有元素數量減一
            count--;
            // 迭代器相關的處理
            if (itrs != null)
                itrs.removedAt(removeIndex);
        }
        // 喚醒notFull條件
        // 喚醒被佇列滿阻塞的執行緒
        notFull.signal();
    }
}

致謝

本部落格為博主的學習實踐總結,並參考了眾多博主的博文,在此表示感謝,博主若有不足之處,請批評指正。

【1】面試官系統精講Java原始碼及大廠真題

【2】死磕 java集合之ArrayBlockingQueue原始碼分析

相關文章