阻塞队列之ArrayBlockingQueue
ArrayBlockingQueue是有界的阻塞队列,创建时需要声明队列容量。其内部只声明了一把锁,说明生产者和消费者是共用这把锁的,那么同一时刻,就只能生产数据或者消费数据了,效率比那种生产者和消费者各自有一把锁的阻塞队列要低。
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();
}
ArrayBlockingQueue的基本属性
// 存放元素的数组
final Object[] items;
// 取元素的索引
int takeIndex;
// 存元素的索引
int putIndex;
// 元素个数
int count;
// 生产者和消费者共用一个锁
final ReentrantLock lock;
// 消费者的Condition
private final Condition notEmpty;
// 生产者的Condition
private final Condition notFull;
ArrayBlockingQueue存放元素的方法
add方法
public boolean add(E e) {
if (offer(e))
// 存放成功,返回true
return true;
else
// 存放失败,抛异常
throw new IllegalStateException("Queue full");
}
offer非阻塞方法
- 判断入参是否为空,如果为空抛出异常
- 加锁
- 判断队列是否已满,如果满了返回false
- 队列未满,将数据放到putIndex指定位置
- 将count+1,并且将putIndex+1
- 判断putIndex值是否超出数组索引范围,如果超出,putIndex设置为0
- 唤醒消费者
- 释放锁
public boolean offer(E e) {
// 检查要存放的元素是否为null,如果是,抛异常
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();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
// 当队列已经满了,存放索引就会移动到队列头部
putIndex = 0;
count++;
// 即时唤醒消费者
notEmpty.signal();
}
offer阻塞方法
- 判断入参是否为空,如果为空抛出异常
- 加锁,加锁的方法可以响应中断
- 这里使用while循环,判断队列是否已满
- 如果队列已满,判断方法的等待时间是否已过,如果已过,返回false
- 方法的等待时间未过,线程阻塞一段时间,并且释放锁
- 线程被唤醒,尝试获取锁,获取锁后,重复执行3、4、5步
- 如果队列未满,将数据放入队列中
- 将putIndex值和count值都+1
- 判断putIndex值是否超出数组索引范围,如果超出,putIndex设置为0
- 唤醒消费者
- 释放锁
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
// 如果存入的元素为null,抛出异常
checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
// lockInterruptibly加锁方法可以响应中断
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
// 当前线程释放锁资源,并且挂起一段时间
// 如果在这段时间中被唤醒,当前线程会竞争锁资源
// 如果成功拿到锁资源,往队列中存放元素
// 如果是时间到了被唤醒,也会竞争锁资源
// 如果成功拿到锁资源,往队列中存放元素
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
put方法
- 判断入参是否为空,如果为空抛出异常
- 加锁,加锁的方法可以响应中断
- 这里使用while循环,判断队列是否已满
- 如果队列已满,线程阻塞等待,并且释放锁
- 线程被唤醒,尝试获取锁,获取锁后,重复执行3、4步
- 如果队列未满,将数据放入队列中
- 将putIndex值和count值都+1
- 判断putIndex值是否超出数组索引范围,如果超出,putIndex设置为0
- 唤醒消费者
- 释放锁
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();
}
}
ArrayBlockingQueue获取元素的方法
remove无参方法
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
poll无参方法
- 加锁
- 判断队列是否为空,如果为空,返回null
- 队列不为空,获取takeIndex索引位置的数据
- 将队列的takeIndex索引位置设置为null
- 将takeIndex+1,并且count-1,判断takeIndex值是否超过队列索引范围
- 如果超过,将takeIndex设置为0
- 唤醒生产者线程
- 释放锁
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;
// 根据takeIndex取出队列中的元素
E x = (E) items[takeIndex];
// 将takeIndex位置的元素设置为null
items[takeIndex] = null;
if (++takeIndex == items.length)
// takeIndex超过队列长度,设置为0
takeIndex = 0;
count--;
// itrs是迭代器内容,先跳过不分析
if (itrs != null)
itrs.elementDequeued();
// 唤醒生产者端被挂起的线程
notFull.signal();
return x;
}
poll有参方法
- 加锁,这里加锁的方法可以响应中断
- while循环,判断队列是否为空,如果为空,判断方法等待时间是否已过
- 如果已过,返回null
- 如果未过,线程阻塞一段时间,并且释放锁
- 线程被唤醒,尝试获取锁,获取到锁后,重复2、3、4步
- 获取takeIndex索引位置的数据
- 唤醒生产者线程
- 释放锁
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)
// 剩余时间<=0,直接返回null
return null;
// 当前线程释放锁资源,并且挂起一段时间
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
take方法
- 加锁,这里加锁的方法可以响应中断
- 判断队列是否为空,如果为空,线程阻塞,并且释放锁
- 线程被唤醒,尝试获取锁,获取到锁后,重复2步
- 获取takeIndex索引位置的数据
- 唤醒生产者线程
- 释放锁
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}