(一)、概述
上一篇文章中,我們介紹了阻塞隊列的接口BlockingQeue,這一篇我們來介紹一下ArrayBlockingQueue的具體實現。
阻塞隊列通常用於生產者——消費者的模式中,當阻塞隊列爲空時,獲取數據的線程會被阻塞,當阻塞隊列滿的時候,插入數據的線程也會被阻塞。
下面我們來看一下ArrayBlockingQueue的具體實現。
(二)、成員變量
//用來存放數據的數組
final Object[] items;
//如果下次要獲取數據,獲取數據的位置
int takeIndex;
//如果下次要插入數據,插入數據的位置
int putIndex;
//數據的總數
int count;
//保證線程安全的鎖
final ReentrantLock lock;
//Condition等待隊列,主要用於獲取數據的線程
private final Condition notEmpty;
//Condition等待隊列,主要用於插入數據的線程
private final Condition notFull;
上面是ArrayBlockingQueue的一些重要的成員變量,下面我們來對這些成員變量進行一些簡要的分析。
- Object數組類型的變量items:從這裏我們可以知道ArrayBlockingQueue是基於數組進行實現的。
- 獲取數據的位置takeIndex:用來標記下一次獲取數據的時候的位置
- 插入數據的位置putIndex:用來標記下一次插入數據的時候的位置
- 數組中所有數據的總數count
- ReentrantLock類型的變量lock:用來保證線程的安全性
- Condition類型的等待隊列notEmpty: 這個等待隊列主要是用來存放需要獲取數據的線程。如果數組items爲空時,就需要進入notEmpty隊列進行等待,直到生產者線程插入數據。
- Condition類型的等待隊列notFull: 這個等待隊列主要是用來存放需要插入的線程。如果數組items已經滿了,就需要進入notFull隊列進行等待,直到消費者取出一些數據後,才能進行插入。
(三)、put方法
下面我們來看一下生產者線程中的一個重要方法:put()方法。主要用於生產者線程進行插入數據。
public void put(E e) throws InterruptedException {
//判斷數組是否爲null
checkNotNull(e);
final ReentrantLock lock = this.lock;
//嘗試獲取鎖(可響應中斷)
lock.lockInterruptibly();
try {
//判斷總數count是否等於數組的長度,如果等於,表示數組滿了
while (count == items.length)
//當前線程進入到notFull等待隊列中
notFull.await();
//當阻塞隊列不滿的時候,進行入隊操作
enqueue(e);
} finally {
//釋放鎖
lock.unlock();
}
}
上面是put()方法的源碼,具體實現應該不難,這裏就不進行過多解釋,看上面的註釋應該很清晰。
可以看到put()方法調用了enqueue()方法進行入隊操作。
private void enqueue(E x) {
//獲得數組
final Object[] items = this.items;
//將元素x存放在數組的putIndex位置上
items[putIndex] = x;
//如果插入的位置已經是數組中的最後一個位置了,那麼就將putIndex置爲0
//因爲已經到達最後一個了,那就只能從第一個位置插入元素
if (++putIndex == items.length)
putIndex = 0;
//總數+1
count++;
//喚醒notEmpty隊列中等待獲取數據的線程
notEmpty.signal();
}
enqueue方法也並不是很難理解,只是簡單的進行入隊操作,所以也不過多解釋了。
(四)、take方法
下面是消費者獲取數據的重要方法:take()方法。主要用在需要獲取數據的線程進行數據的獲取
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
//嘗試獲得鎖(可響應中斷)
lock.lockInterruptibly();
try {
//如果隊列爲空
while (count == 0)
//進入到notEmpty隊列進行等待
notEmpty.await();
//返回獲取的元素
return dequeue();
} finally {
//釋放鎖
lock.unlock();
}
}
take方法的邏輯也不難,這裏也不解釋太多,我們直接來看dequeue()方法:
private E dequeue() {
//獲取數組
final Object[] items = this.items;
@SuppressWarnings("unchecked")
//獲得takeIndex位置上的數據
E x = (E) items[takeIndex];
//將takeIndex位置置爲null
items[takeIndex] = null;
//判斷是否是獲取最後一個位置的數據,如果是,下一次就需要從0開始獲取
if (++takeIndex == items.length)
takeIndex = 0;
//總數減一
count--;
//這個是itrs類實現的迭代器,是ArrayBlcokingQueue的一個內部類
if (itrs != null)
itrs.elementDequeued();
//因爲取出了一個元素,所以隊列中肯定要空位置,於是喚醒需要插入數據的線程。
notFull.signal();
return x;
}