標籤:
我們到底能走多遠系列(42)
扯淡:
乘著有空,讀些juc的源碼學習下。後續把juc大致走一邊,反正以後肯定要再來。
主題:
BlockingQueue 是什麼A
java.util.Queue
that additionally supports operations that wait for the queue to become non-empty when retrieving an element, and wait for space to become available in the queue when storing an element. 一個能阻塞的隊列在兩個操作隊列時的阻塞: 1,擷取隊列中元素時,隊列為空白,則阻塞,直到隊列中有元素。 2,存放一個元素時,隊列已滿,則阻塞,直到隊列中有空位置可以存放。BlockingQueue 作為介面規定了實現的規矩。下面是隊列核心的存取操作方法的4個種類:
|
Throws exception |
Special value |
Blocks |
Times out |
Insert |
add(e) |
offer(e) |
put(e) |
offer(e, time, unit) |
Remove |
remove() |
poll() |
take() |
poll(time, unit) |
Examine |
element() |
peek() |
not applicable |
not applicable |
根據上面表,在隊列滿或空時的策略分別包含了,拋出異常,返回boolean值,阻塞線程,阻塞到逾時。為什麼要這麼選擇,就不清楚了。我們需要注意的是除了第三種,其他方法都沒有真正阻塞線程。
ArrayBlockingQueue:內部用數組實現的一個queue,按照元素先進先出(FIFO)原則。初始化後,隊列容量不可改變。支援可選的公平機制,來保證阻塞的操作線程能按照順序排列等待。預設是不公平機制。
源碼實現:1,使用Object[]的一個數組來儲存元素
// 隊列存放元素的容器final Object[] items;// 下一次讀取或移除的位置int takeIndex;// 存放下一個放入元素的位置int putIndex;// 隊列裡有效元素的數量int count;// 所有訪問的保護鎖final ReentrantLock lock;// 等待擷取的條件private final Condition notEmpty;// 等待放入的條件private final Condition notFull;
2,整個隊列是有一個環繞機制的,比如這時候我一直取資料,那麼讀取的下標會一直後移,知道數組的末尾。如果這時候制定數組的尾部後一個下標時數組的頭位。如此即實現環繞的一個隊列。如此實現十分精妙,可說是整個隊列實現的基礎機制。
如此,這個隊列的容量是不可改變的。
// 指標前移final int inc(int i) { return (++i == items.length) ? 0 : i;}// 指標後移final int dec(int i) { return ((i == 0) ? items.length : i) - 1;}
3,直接看下核心的put和take方法實現:
put
public void put(E e) throws InterruptedException { checkNotNull(e);//不能放null final ReentrantLock lock = this.lock;//先把鎖賦給final修飾的局部變數 // 在JUC的很多類裡,都會看到這種寫法:把類的屬性賦值給方法內的用final修飾一個變數。 // 這是因為類的屬性是存放在堆裡的,方法內的變數是存放在方法棧上的,存取方法棧比訪問堆要快。 // 在這裡,this.lock屬性要訪問兩次,通過賦值給方法的局部變數,就節省了一次堆的訪問。 // 其他的類屬性只訪問一次就不需要這樣處理了。 lock.lockInterruptibly();//加鎖 try { //迴圈保證避免避免虛假喚醒,虛假喚醒就是此事如果有多個線程都wait,
//而被同時喚醒時都會去執行下面的insert //如果在while迴圈中,那麼喚醒後先判斷count大小,來確定是繼續wait還是insert。 while (count == items.length) notFull.await();//阻塞線程 insert(e); } finally { lock.unlock();//釋放鎖 } }
take
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return extract(); } finally { lock.unlock(); } }
其中使用到insert和extract方法,當然也可以看到只有持有鎖的情況下才會調用這兩個方法,如此這個方法的調用不需要關係是否安全執行緒,調用前保證安全執行緒:
private void insert(E x) { items[putIndex] = x;// 1,存值,非常簡便 putIndex = inc(putIndex);//2,移動下標,使用inc方法 ++count;//3,增加元素總數 notEmpty.signal();//4,通知在非空條件上等待的讀線程 }
private E extract() { final Object[] items = this.items;//先將類變數賦給方法變數,前面提過這個用處 E x = this.<E>cast(items[takeIndex]); items[takeIndex] = null; takeIndex = inc(takeIndex); --count; notFull.signal(); return x; }
操作:
1,一個環的數組
2,再放一個元素:
3,取一個元素
當然ArrayBlockingQueue裡還有其他方法,這裡就不贅述了。有興趣的同學可以深入繼續探索。
總結:
1,一個環的數組設計十分巧妙。
2,將類變數賦給方法變數的編碼方式
讓我們繼續前行
----------------------------------------------------------------------
努力不一定成功,但不努力肯定不會成功。
ArrayBlockingQueue-我們到底能走多遠系列(42)