前言
ArrayBlockingQueue從名字上我們就可以知道:以數組實現的阻塞隊列。它是線程安全的,滿足隊列的特性:先進先出。下面我們來分析下它的源碼,瞭解下它的實現過程。
1、屬性
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
//存放元素的數組
final Object[] items;
//取指針:指向下一個待取出元素地址
int takeIndex;
//放指針:指向下一個待添加元素地址
int putIndex;
//隊列裏面元素的數量
int count;
//獨佔鎖
final ReentrantLock lock;
//非空條件
private final Condition notEmpty;
//非滿條件
private final Condition notFull;
}
2、構造方法
public ArrayBlockingQueue(int capacity) {
//初始化隊列容量,默認非公平鎖
this(capacity, false);
}
//初始化隊列容量,指定是否是公平鎖
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
//初始化獨佔鎖
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) {
checkNotNull(e);
items[i++] = e;
}
//數組容量初始化之後就是固定不變的
} catch (ArrayIndexOutOfBoundsException ex) {
throw new IllegalArgumentException();
}
count = i;
putIndex = (i == capacity) ? 0 : i;
} finally {
lock.unlock();
}
}
3、入隊
入隊有四個方法,分別是add(E e)、offer(E e)、put(E e)、offer(E e, long timeout, TimeUnit unit),下面來看看它們的區別:
public boolean add(E e) {
//調用父類的add(e)方法
return super.add(e);
}
//super.add(e)方法:
public boolean add(E e) {
// 調用offer(e)方法,如果成功返回true
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
public boolean offer(E e) {
//檢查元素是否爲空
checkNotNull(e);
final ReentrantLock lock = this.lock;
//獲取獨佔鎖,保證線程安全
lock.lock();
try {
//如果數組滿了
if (count == items.length)
//返回false
return false;
else {
//數組沒滿,就入隊列
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需要等待數組中取出一個元素後才能操作
//進行等待,這裏可能有多個線程阻塞在lock上面
notFull.await();
//入隊列
enqueue(e);
} finally {
lock.unlock();
}
}
//這裏多加了一個等待超時機制,其它和put一樣
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 {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
//入隊列:利用指針的循環來存放元素
private void enqueue(E x) {
final Object[] items = this.items;
// 把元素放在放指針的位置上
items[putIndex] = x;
// 如果放指針等於數組長度,就返回頭部
if (++putIndex == items.length)
putIndex = 0;
// 數組數量加1
count++;
// 入隊了一個元素,所以喚醒notEmpty去取出元素
notEmpty.signal();
}
4、出隊
出隊也有四個方法,分別是remove()、poll()、take()、poll(long timeout, TimeUnit unit),我們也來看看它們的區別:
public E remove() {
// 調用poll()方法出隊,返回出隊的元素
E x = poll();
if (x != null)
return x;
else
//沒有出隊的則拋出異常
throw new NoSuchElementException();
}
public E poll() {
final ReentrantLock lock = this.lock;
// 加鎖
lock.lock();
try {
//隊列個數爲0,則返回null,否則出隊
return (count == 0) ? null : dequeue();
} finally {
//釋放鎖
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
// 加鎖
lock.lockInterruptibly();
try {
// 隊列個數爲0
while (count == 0)
//阻塞等待在條件notEmpty上
notEmpty.await();
//有元素,則出隊
return dequeue();
} finally {
// 解鎖
lock.unlock();
}
}
//同take,加了阻塞的超時時間
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
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;
@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條件
notFull.signal();
return x;
}
5、總結
- ArrayBlockingQueue長度是固定的,在初始化的時候指定,所以要慎重考慮長度。
- ArrayBlockingQueue是線程安全的,利用了ReentrantLock和兩個Condition條件來保證併發安全。
- ArrayBlockingQueue在入隊和出隊都分別定義了:拋出異常,有返回值,阻塞,超時,四類方法來保證不同的場景用途。
結束語
前一篇,我們學習了ConcurrentHashMap的分段鎖,出隊和入隊是否可以使用分段鎖,如果讓你實現,你怎麼實現呢?