文章目录
ArrayBlockingQueue原理探究
ArrayBlockingQueue是使用有界数组方式实现的阻塞队列.
(1). 结构
ArrayBlockingQueue内部有一个数组items,用来存放队列元素.outIndex用来存放入队元素下标,tackIndex用来存放出队元素下标.这些变量并没有使用volatile修饰,因为加锁已经保证了这些变量在锁内的可见性了.
独占锁lock用来保证出入队操作的原子性,notEmpty,notFull连个条件变量用来进行出入队的同步.
ArrayBlockingQueue是有界的,所以构造必须传入队列大小为参数.默认情况下使用非公平锁.
(2). ArrayBlockingQueue原理介绍
1). offer操作
插入元素,如果队列已满,则丢弃元素,不会阻塞线程.
public boolean offer(E e) {
// 判断元素是否为空,为空抛出异常
checkNotNull(e);
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 判断队列是否满,count为队列中已填充元素数量
// items.length为数组长度,也就是队列的最大值
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
// 方法内部将在putIndex位置上放置新元素,并将putIndex++,如果越界重置为0(循环数组)
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
2). put操作
向尾部插入一个元素,如果有空闲,插入,如果队列已满,阻塞等待队列出现空闲.
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(); //解锁
}
}
3). poll操作
从队列头部移除一个元素,如果队列为空,返回null,不会阻塞等待队列不为空
public E poll() {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 如果队列为空,返回null
// 如果不为空,进行删除元素的操作,并将该元素返回
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();//解锁
}
}
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--;
// itrs是当前活动迭代器的共享状态;如果已知没有状态,则为null。
if (itrs != null)
// 更新迭代器中的元素数据
itrs.elementDequeued();
// 唤醒因为队列满导致没有入队成功的入队线程
notFull.signal();
return x;
}
4). take操作
获取当前队列头部元素,并删除它,如果队列为空,会阻塞等待队列不为空时进行操作
可响应中断.
public E take() throws InterruptedException {
// 获取锁对象,并响应中断式的加锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 循环判断队列是否为空,为空则条件阻塞
while (count == 0)
notEmpty.await();
// 删除并返回头部元素
return dequeue();
} finally {
lock.unlock();
}
}
5). peek操作
获取头部元素,但是不移除,如果队列为空,返回null
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 直接返回出队下标处的元素
return itemAt(takeIndex);
} finally {
lock.unlock();
}
}
// 获取对应下表处的元素
final E itemAt(int i) {
return (E) items[i];
}
(3). 小结
ArrayBlockingQueue使用一个独占锁来实现只能有一个线程进行入队和出队操作,这个锁的粒度比较大,类似于在方法上加synchronized.