【JDK原始碼分析系列】ArrayBlockingQueue原始碼分析
【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原始碼及大廠真題
相關文章
- 原始碼分析系列1:HashMap原始碼分析(基於JDK1.8)原始碼HashMapJDK
- JDK原始碼分析-TreeSetJDK原始碼
- 原始碼|jdk原始碼之HashMap分析(一)原始碼JDKHashMap
- 原始碼|jdk原始碼之HashMap分析(二)原始碼JDKHashMap
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(1)JDK原始碼
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(2)JDK原始碼
- 【JDK】JDK原始碼分析-AbstractQueuedSynchronizer(3)JDK原始碼
- 【JDK】JDK原始碼分析-ReentrantLockJDK原始碼ReentrantLock
- 死磕 jdk原始碼之HashMap原始碼分析JDK原始碼HashMap
- JDK 1.6 HashMap 原始碼分析JDKHashMap原始碼
- jdk原始碼分析之TreeMapJDK原始碼
- HashMap原始碼分析(JDK 1.8)HashMap原始碼JDK
- jdk原始碼分析之PriorityQueueJDK原始碼
- jdk原始碼分析之WeakHashMapJDK原始碼HashMap
- jdk原始碼分析之ArrayListJDK原始碼
- jdk原始碼分析之HashMapJDK原始碼HashMap
- jdk原始碼分析之CopyOnWriteArrayListJDK原始碼
- Spark 原始碼分析系列Spark原始碼
- JDK1.8 hashMap原始碼分析JDKHashMap原始碼
- HashMap原始碼分析 JDK1.8HashMap原始碼JDK
- ArrayList原始碼分析(JDK1.8)原始碼JDK
- 原始碼分析–HashSet(JDK1.8)原始碼JDK
- 原始碼分析–ArrayList(JDK1.8)原始碼JDK
- JDK原始碼分析(四)——LinkedHashMapJDK原始碼HashMap
- JDK 原始碼分析(1) Object類JDK原始碼Object
- HashMap原始碼分析(JDK8)HashMap原始碼JDK
- ArrayList原始碼分析 jdk1.8原始碼JDK
- jdk原始碼分析之LinkedListJDK原始碼
- jdk原始碼分析之ConcurrentHashMapJDK原始碼HashMap
- jdk原始碼分析之LinkedHashMapJDK原始碼HashMap
- 原始碼|jdk原始碼之棧、佇列及ArrayDeque分析原始碼JDK佇列
- DelayQueue系列(一):原始碼分析原始碼
- Retrofit原始碼分析三 原始碼分析原始碼
- JDK1.8 原始碼分析(十) -- TreeMapJDK原始碼
- LinkedList原始碼分析(jdk1.8)原始碼JDK
- JDK1.8原始碼分析之HashMapJDK原始碼HashMap
- jdk1.8原始碼之HashMap分析JDK原始碼HashMap
- 集合原始碼分析[2]-AbstractList 原始碼分析原始碼