java併發面試常識之ArrayBlockingQueue
ArrayBlockingQueue是常用的執行緒集合,線上程池中也常常被當做任務佇列來使用。使用頻率特別高。他是維護的是一個迴圈佇列(基於陣列實現),迴圈結構在資料結構中比較常見,但是在原始碼實現中還是比較少見的。
執行緒安全的實現
執行緒安全佇列,基本是離不開鎖的。ArrayBlockingQueue使用的是ReentrantLock,配合兩種Condition,實現了集合的執行緒安全操作。這裡稍微說一個好習慣,下面是成員變數的宣告。
private static final long serialVersionUID = -817911632652898426L;
final Object[] items;
int takeIndex;
int putIndex;
int count;
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
transient Itrs itrs = null;
賦值的操作基本都是在建構函式裡做的。這樣有個好處,程式碼執行可控。成員變數的初始化也是會合並在構造方法裡執行的,但是在執行順序上需要好好斟酌,如果寫在構造方法裡初始化,則沒有相關問題。
阻塞佇列的常用場所就是生產者消費者。一般都是生產者放入,消費者從頭取資料。下面重點說這兩個操作。
這兩個操作都是依靠鎖來保證執行緒安全的。
生產操作
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();
}
}
put等放入操作,首先是獲取鎖,如果發現資料滿了,就通過notFull的condition,來阻塞執行緒。這裡的條件判定一定是用while而不是if,多執行緒情況下,可以被喚醒後發現又滿了。
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
這個是入佇列的操作。首先獲取維護的陣列。putindex就是放入操作的標誌。這個操作會一直加。達到預定的長度後就變成0從頭開始計數。這樣插入的操作就是一個迴圈的操作了,count就是用來做計數的,作為能否插入資料的一個標準,插入資料後就通過notEmpty的condition發出一個訊號喚醒消費執行緒。
消費操作
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
消費的方法也是這樣。先獲取鎖,然後進行條件判斷,如果沒有資料,則阻塞執行緒。注意點和put一樣。
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
取資料的時候,也依靠takeIndex,這是一個標誌,這個數值也會一直增加,表示取的第一個資料的位置。如果這個標誌走到最後,然後變成0,從頭再來。這樣保證取出的資料都是fifo的順序。刪除的時候如果發現迭代中,則會修改迭代器的遍歷。然後通過notFull的condition來喚醒生產執行緒。
移除操作
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 int putIndex = this.putIndex;
int i = takeIndex;
do {
if (o.equals(items[i])) {
removeAt(i);
return true;
}
if (++i == items.length)
i = 0;
} while (i != putIndex);
}
return false;
} finally {
lock.unlock();
}
}
對於remove操作就比較麻煩了,首先獲取鎖之後,把兩個標誌位本地化,然後找到要刪除的元素的位置。呼叫removeAt,這裡刪除需要對標誌位做改變。
void removeAt(final int removeIndex) {
final Object[] items = this.items;
if (removeIndex == takeIndex) {
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
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 {
items[i] = null;
this.putIndex = i;
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
notFull.signal();
}
如果刪除的元素是位置和takeindex一樣。那就可以直接刪除,然後讓刪除標誌位向後移動。如果不是,則從刪除的位置開始,進行後面向前面的資料覆蓋的操作。直到遇到putindex的前一個位置。然後把那個位置的資料設定為null。並且把putindex的位置往前移動一格,正在迭代的時候要刪除資料並且喚醒生產執行緒。
相關文章
- java併發面試常識之copyonwriteJava面試
- 【搞定 Java 併發面試】面試最常問的 Java 併發基礎常見面試題總結!Java面試題
- Java併發面試題Java面試題
- Java併發包原始碼學習系列:阻塞佇列實現之ArrayBlockingQueue原始碼解析Java原始碼佇列BloC
- java併發程式設計工具類JUC第二篇:ArrayBlockingQueueJava程式設計BloC
- 併發容器之ArrayBlockingQueue和LinkedBlockingQueue實現原理詳解BloC
- Java 併發面試題解Java面試題
- 面試系列<3>——java併發面試Java
- Java併發面試題精選Java面試題
- java併發之synchronizedJavasynchronized
- Java併發之ExecutorJava
- java併發之CopyOnWriteArrayListJava
- 面試侃集合 | ArrayBlockingQueue篇面試BloC
- 常見Java面試知識點總結Java面試
- Java併發--基礎知識Java
- Java 多執行緒應用 之 ArrayBlockingQueueJava執行緒BloC
- java併發程式設計系列:java併發程式設計背景知識Java程式設計
- java 多執行緒 併發 面試Java執行緒面試
- Java併發包之 CopyOnWriteArrayListJava
- Java併發之ConcurrentHashMapJavaHashMap
- Java 面試必會知識點:Java 多執行緒與併發程式設計Java面試執行緒程式設計
- [ Java面試題 ]Java 開發崗面試知識點解析Java面試題
- 再識Java併發程式設計關鍵字之volatileJava程式設計
- Java併發--Java執行緒面試題 Top 50Java執行緒面試題
- java面試-Java併發程式設計(五)——中斷Java面試程式設計
- java併發之ConcurrentLinkedQueueJava
- java併發之hashmap原始碼JavaHashMap原始碼
- Java併發之AQS原理剖析JavaAQS
- Java併發之AQS詳解JavaAQS
- Java併發系列之volatileJava
- Java 併發包之CountDownLatch、CyclicBarrierJavaCountDownLatch
- Java併發之顯式鎖Java
- Java之併發三問題Java
- 併發學習計劃-ArrayBlockingQueue和LinkedBlockingQueue02BloC
- 執行緒基礎(二十一)-併發容器-ArrayBlockingQueue(上)執行緒BloC
- Java集合類常見面試知識點總結Java面試
- 【Java面試】Java常見IO面試題!Java面試題
- Golang 基礎之併發知識 (三)Golang