之前介绍的关于Queue的实现类,LinkedList、PriorityQueue都是非线程安全的队列,那么有没有线程安全的Queue实现类呢?
当然是有的,在java.util.concurrent中有几个关于Queue的线程安全实现
/**
* @see java.util.concurrent.ArrayBlockingQueue
* @see java.util.concurrent.LinkedBlockingQueue
* @see java.util.concurrent.PriorityBlockingQueue
*/
由名称可以看出来,ArrayBlockingQueue是基于数组实现的;LinkedBlockingQueue是基于链表实现的;PriorityBlockingQueue是基于堆实现的优先级队列
实现方式都是差不多的,只是底层结构不一样,之前的几篇博客已经分析过这些数据结构的不同了,在这里就不再继续分析了,焦点就关注在如何实现线程安全。
本篇博客来分析一下比较典型的ArrayBlockingQueue
/**
* A bounded {@linkplain BlockingQueue blocking queue} backed by an
* array. This queue orders elements FIFO (first-in-first-out).
*/
public class ArrayBlockingQueue extends AbstractQueue
implements BlockingQueue, java.io.Serializable {
// 基于数组存储
/** The queued items */
final Object[] items;
// 下一个获取值的索引
/** items index for next take, poll, peek or remove */
int takeIndex;
// 下一个存放值的索引
/** items index for next put, offer, or add */
int putIndex;
// 队列中元素数量
/** Number of elements in the queue */
int count;
// 锁
/** Main lock guarding all access */
final ReentrantLock lock;
// 锁的条件队列(非空,如果队列已空,则再执行删除元素的是会在此等待)
/** Condition for waiting takes */
private final Condition notEmpty;
// 锁的条件队列(未满,如果队列已满,则再执行添加元素的是会在此等待)
/** Condition for waiting puts */
private final Condition notFull;
笔者把对应的英文注释也拷贝过来了,实际直接看英文注释基本就已经理解了相关成员变量的含义了
// 1.指定容量构造
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
// 2.指定容量和锁的类型
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();
}
// 3.指定容量、锁的类型、队列初始值
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();
}
}
构造方法主要是初始化锁和锁的条件判断
// ArrayBlockingQueue.add()
public boolean add(E e) {
// 直接调用父类的add方法
return super.add(e);
}
// AbstractQueue.add()
public boolean add(E e) {
// 又直接调用了子类的offer方法
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
// ArrayBlockingQueue.offer()
public boolean offer(E e) {
// 1.判断对象是否为空,如果为空,则直接抛错
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 2.获取锁对象
lock.lock();
try {
// 3.如果数组已满,则直接返回false
if (count == items.length)
return false;
else {
// 4.执行添加操作
enqueue(e);
return true;
}
} finally {
// 释放锁
lock.unlock();
}
}
// ArrayBlockingQueue.enqueue()
private void enqueue(E x) {
final Object[] items = this.items;
// 1.直接在putIndex上存放该值x
items[putIndex] = x;
// 2.如果putIndex+1之后等于数组长度,说明当前添加的元素占据了数组的最后一个位置
// 就需要进行一次轮回,将putIndex重新置为0
if (++putIndex == items.length)
putIndex = 0;
// 4.count自增
count++;
// 5.这一步很关键,可能有其他线程在执行获取元素的时候,停在了notEmpty.await()上了
// 所以需要将其唤醒
notEmpty.signal();
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
// 1.获取锁对象,
// lockInterruptibly是可被中断的
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 2.如果队列已满,则执行await方法,在这里等待
// 一直到有线程执行notFull.signal或者当前线程中断
while (count == items.length)
notFull.await();
// enqueue()方法上面已经分析过
enqueue(e);
} finally {
// 3.释放对象锁
lock.unlock();
}
}
有关于Condition.await()方法,我们需要注意的是,它会自动释放获取的对象锁,转而将当前线程挂起,直到其他线程执行Condition.signal()或者当前线程被中断
总结:关于添加的这三个方法
* add 队列已满时会抛出异常
* offer 队列已满时返回false,执行成功则返回true,针对于null值的添加抛出NullPointException
* put 队列已满时则阻塞
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items;
// 1.获取锁对象
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count > 0) {
final int putIndex = this.putIndex;
// 2.从takeIndex向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 {
// 3.释放对象锁
lock.unlock();
}
}
// ArrayBlockingQueue.removeAt(int index)
void removeAt(final int removeIndex) {
final Object[] items = this.items;
// 1.如果需要删除的刚好是takeIndex所在的那一位,则直接将该位数据置空即可
if (removeIndex == takeIndex) {
// removing front item; just advance
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
} else {
// 2.如果需要删除的数据再takeIndex和putIndex之间,
// 那么就需要将removeIndex和putIndex之间的数据向前挪一位
final int putIndex = this.putIndex;
for (int i = removeIndex;;) {
int next = i + 1;
if (next == items.length)
next = 0;
if (next != putIndex) {
items[i] = items[next];
i = next;
} else {
items[i] = null;
this.putIndex = i;
break;
}
}
count--;
if (itrs != null)
itrs.removedAt(removeIndex);
}
// 3.删除完之后,唤醒那些添加时阻塞的线程
notFull.signal();
}
public E poll() {
// 1.获取锁对象
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 2.如果队列为空则直接返回null,否则执行dequeue()
return (count == 0) ? null : dequeue();
} finally {
// 3.释放对象锁
lock.unlock();
}
}
// dequeue()
private E dequeue() {
final Object[] items = this.items;
@SuppressWarnings("unchecked")
// 1.记得需要满足队列的特性,先进先出,
// 所以获取元素,takeIndex从0开始
E x = (E) items[takeIndex];
// 获取之后将takeIndex所在元素置为null
items[takeIndex] = null;
// 2.如果takeIndex自增之后等于数组长度,说明已经取到最后一个元素了
// 这时需要进行一次轮回,重新从0开始
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
// 3.很重要的一步,唤醒存放值时被沉睡的线程
notFull.signal();
return x;
}
public E take() throws InterruptedException {
// 1.获取锁对象,可被中断
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 2.如果数组尾空,则执行等待
// 可以与offer方法对应着看
while (count == 0)
notEmpty.await();
// 3.执行dequeue方法,上面已经分析过
return dequeue();
} finally {
lock.unlock();
}
}
总结:
* remove 返回true或false
* poll 如果队列为空则返回null
* take 如果队列为空则执行等待,一直到有数据进入队列或者线程中断
// ArrayBlockingQueue.peek()
public E peek() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 直接获取takeIndex的数据
return itemAt(takeIndex); // null when queue is empty
} finally {
lock.unlock();
}
}
// ArrayBlockingQueue.itemAt()
final E itemAt(int i) {
return (E) items[i];
}
总结:
https://blog.csdn.net/wei_ya_wen/article/details/19344939 这篇博客有一个关于这些方法的总结觉得不错,大家可以参考下
疑问:这个remove()抛出异常,实在没明白怎么抛异常的?