ArrayBlockingQueue閱讀筆記
一、簡介
一種阻塞的隊列,添加和移除元素都會進行ReentrantLock加鎖,其中的take和put方法利用了condition條件鎖來讓添加和獲取可以讓線程等待有元素或有空位置再取或加
二、繼承關係圖
三、存儲結構
- 不需要擴容。初始化指定了容量,並循環利用數組,加上添加和取出元素都加鎖
四、源碼分析
內部類
屬性
/** 用於存儲元素的對象數組 */
final Object[] items;
/** 取元素的指針 */
int takeIndex;
/** 放元素的指針 */
int putIndex;
/** 隊列中元素的數量 */
int count;
/** 保證併發訪問的鎖 */
final ReentrantLock lock;
/** 非空條件:用於隊列元素數量爲 0 時,take元素的等待條件鎖 */
private final Condition notEmpty;
/** 非滿條件:用於隊列元素數量達到最大時,put新的元素的等待鎖 */
private final Condition notFull;
構造
/** 構造方法一:初始化隊列最大容量 */
public ArrayBlockingQueue(int capacity) {
// 默認非公平
this(capacity, false);
}
/** 構造方法二:初始化隊列最大容量、公平/非公平模式選擇 */
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
// 初始化ReentrantLock鎖、非空條件鎖、非滿條件鎖
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
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 {
int i = 0;//開始添加元素的索引
try {
for (E e : c) {
// null檢查,如果是null拋NullPointerException異常
checkNotNull(e);
// 把元素加入到隊列中
items[i++] = e;
}
} catch (ArrayIndexOutOfBoundsException ex) {
// 如果初始化的元素數量超出 可添加元素數量則拋出IllegalArgumentException異常
throw new IllegalArgumentException();
}
// 此時的i也是隊列數組中的元素數量,所以賦值給count
count = i;
// 也是下一次添加元素需要放置的位置
// 如果 i 等於對象數組的大小,則把添加元素的指針放在索引 0,否則直接放在 i的位置
putIndex = (i == capacity) ? 0 : i;
} finally {
// 釋放鎖
lock.unlock();
}
}
主要方法
1、添加元素
- add(E e),如果隊列滿了就拋出異常:IllegalStateException(“Queue full”)
- offer(E e),如果隊列滿了返回false,
- put(E e,long timeout,TimeUtil timeunit),如果隊列滿了則使用notFull等待
- offer(E e),如果隊列滿了,則等待一算時間後 隊列中元素數量
// 添加元素,成功true,失敗會拋出異常
public boolean add(E e) {
// 調用AbstractQueue.add(E e)方法
return super.add(e);
}
// AbstractQueue.add(E e)
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
// 添加元素,成功true,失敗flase
public boolean offer(E e) {
// 元素null檢查,如果爲null拋出異常
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 加鎖
lock.lock();
try {
// 如果元素滿,則返回false,所以只有元素滿的情況纔會返回false
if (count == items.length)
return false;
else {
// 添加元素
enqueue(e);
return true;
}
} finally {
// 釋放鎖
lock.unlock();
}
}
public void put(E e) throws InterruptedException {
// null 檢查
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 加鎖,如果線程中斷了拋出異常
lock.lockInterruptibly();
try {
// 如果數組滿了,就是用notFull鎖等待
// 等隊列取出一個元素的時候,再喚醒取元素
while (count == items.length)
notFull.await();
// 把元素加入對象數組
enqueue(e);
} finally {
// 釋放鎖
lock.unlock();
}
}
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
checkNotNull(e);
// 獲取納秒
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
// 加鎖
lock.lockInterruptibly();
try {
// 如果數組滿了,就阻塞nanos納秒
// 如果喚醒這個線程時依然沒有空間且時間到了就返回flase
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
// 入隊
enqueue(e);
return true;
} finally {
// 釋放鎖
lock.unlock();
}
}
// 把元素添加到putIndex,並把putIndex指向數組隊列下一個元素
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
// 引用當前集合的對象數組
final Object[] items = this.items;
// 把元素放在putIndex指針下
items[putIndex] = x;
// 如果放元素指針達到了盡頭,就重新指向第一個位置
if (++putIndex == items.length)
putIndex = 0;
// 元素熟練 + 1
count++;
// 喚醒notEmpty鎖等待隊列中的線程,也就是取元素的等待鎖,告訴條件鎖,可以取元素了
notEmpty.signal();
}
2、獲取元素
-
remove():如果隊列爲null則拋出異常
-
poll():如果隊列爲null則返回null
-
take(): 如果隊列爲null則阻塞等待在條件notEmpty的等待隊列
-
poll(long,TimeUnit):如果隊列爲null則阻塞等待納秒時間後如果還爲null就返回null
-
peek():利用取指針循環從數組中取出元素
/*
雖然是刪除(是父類AbstractQueue的方法,但是通過模板模式調用了自身的poll()函數),但是也是調用的poll(),如果返回結果爲null,會拋出NoSuchElementException異常,有元素就返回對應元素
*/
public E remove() {
// 直接調用poll
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
/*
取出元素,如果沒有元素則返回null,否則返回取出元素對象(同時會喚醒notFull條件鎖),如果有元素,返回對應元素,並把takeIndex + 1
*/
public E poll() {
final ReentrantLock lock = this.lock;
// 加鎖
lock.lock();
try {
// 如果元素爲0,則返回null,否則執行dequeue取出元素
return (count == 0) ? null : dequeue();
} finally {
// 釋放鎖
lock.unlock();
}
}
/*
取出元素,如果元素中沒有對象,則直接把線程加入到notEmpty條件鎖鏈表隊列尾,返回對應元素,並把takeIndex + 1
*/
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
// 加鎖,如果線程中斷則拋出異常
lock.lockInterruptibly();
try {
while (count == 0)
// 如果隊列中沒有元素,則加入notEmpty等待隊列,等待添加新的元素喚醒
notEmpty.await();
// 取出元素,並喚醒notFull中的等待隊列first線程
return dequeue();
} finally {
// 釋放鎖
lock.unlock();
}
}
/*
取出元素,如果隊列沒有元素,則會等待timeout納秒,如果在timeout納秒內還沒有獲取元素,就直接返回null,如果有元素則返回對應元素,並把takeIndex + 1
*/
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
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();
}
}
/*
獲取元素,有則返回元素,沒有返回null,此方法不會對元素移除(也就是改變takeIndex值)
*/
public E peek() {
final ReentrantLock lock = this.lock;
// 加鎖
lock.lock();
try {
// 直接返回元素,如果沒有返回null
return itemAt(takeIndex); // null when queue is empty
} finally {
// 解鎖
lock.unlock();
}
}
// 把takeIndex下的元素設置爲null,並把takeIndex指向數組隊列下一個元素
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
// 取出元素,並設置爲null
items[takeIndex] = null;
if (++takeIndex == items.length)
// 取索引指向下一個元素
// 如果更新後的取索引 超出元素下標,則設置爲0
takeIndex = 0;
// 元素個數 - 1
count--;
if (itrs != null)
itrs.elementDequeued();
// 喚醒notFull條件鎖中的first線程,notFull 隊列慢的時候的等待鎖
notFull.signal();
return x;
}
3、刪除元素
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();
}
}
五、總結
- 不需要擴容,初始化指定了容量
- 添加和獲取元素都加鎖,利用了takeIndex和putIndex循環利用數組
- 入隊和出隊定義了四種方法來滿足不同用途