不允許還有Java程序員不了解BlockingQueue阻塞隊列的實現原理( 二 )


4. BlockingQueue源碼解析BlockingQueue的5種子類實現方式大同小異,這次就以最常用的ArrayBlockingQueue做源碼解析 。
4.1 ArrayBlockingQueue類屬性先看一下ArrayBlockingQueue類里面有哪些屬性:
// 用來存放數據的數組final Object[] items;// 下次取數據的數組下標位置int takeIndex;// 下次放數據的數組下標位置int putIndex;// 當前已有元素的個數int count;// 獨占鎖,用來保證存取數據安全final ReentrantLock lock;// 取數據的條件private final Condition notEmpty;// 放數據的條件private final Condition notFull;ArrayBlockingQueue中4組存取數據的方法實現也是大同小異,本次以put和take方法進行解析 。
4.2 put方法源碼解析

不允許還有Java程序員不了解BlockingQueue阻塞隊列的實現原理

文章插圖
無論是放數據還是取數據都是從隊頭開始,逐漸往隊尾移動 。
// 放數據,如果隊列已滿,就一直阻塞,直到有其他線程從隊列中取走數據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;// 元素數量加一count++;// 喚醒因為隊列空等待的線程notEmpty.signal();}源碼中有個有意思的設計,添加元素的時候如果已經到了隊尾,下次就從隊頭開始添加,相當于做成了一個循環隊列 。
像下面這樣:
不允許還有Java程序員不了解BlockingQueue阻塞隊列的實現原理

文章插圖
4.3 take方法源碼// 取數據,如果隊列為空,就一直阻塞,直到有其他線程往隊列中放數據public E take() throws InterruptedException {final ReentrantLock lock = this.lock;// 加鎖,加可中斷的鎖lock.lockInterruptibly();try {// 如果隊列為空,就一直阻塞,直到被喚醒while (count == 0)notEmpty.await();// 如果隊列不為空,就從隊列取數據return dequeue();} finally {// 結束后,別忘了釋放鎖lock.unlock();}}// 實際從隊列取數據的方法private E dequeue() {// 獲取數組final Object[] items = this.items;// takeIndex 表示本次取數據的位置,是上一次取數據時計算好的E x = (E) items[takeIndex];// 取完之后,就把隊列該位置的元素刪除items[takeIndex] = null;// ++takeIndex 計算下次取數據的位置// 如果本次取數據的位置,正好是隊尾,下次就從 0 開始取數據if (++takeIndex == items.length)takeIndex = 0;// 元素數量減一count--;if (itrs != null)itrs.elementDequeued();// 喚醒被隊列滿所阻塞的線程notFull.signal();return x;}4.4 總結
  1. ArrayBlockingQueue基于數組實現的阻塞隊列,創建隊列時需指定容量大小,是有界隊列 。
  2. ArrayBlockingQueue底層采用循環隊列的形式,保證數組位置可以重復使用 。
  3. ArrayBlockingQueue存取都采用ReentrantLock加鎖,保證線程安全,在多線程環境下也可以放心使用 。
  4. 使用ArrayBlockingQueue的時候,預估好隊列長度,保證生產者和消費者速率相匹配 。
  5. 經驗總結擴展閱讀