什么是阻塞队列
阻塞队列是J.U.C中提供的一个阻塞工具,相信大家对于生产者消费者模式都比较熟悉,而阻塞队列就可以很好的去实现这种设计模式,来进行相应场景下的解耦。
在J.U.C中提供了7个阻塞队列(Java8)分别是
种类 | 详述 |
---|---|
ArrayBlockingQueue | 数组实现的有界阻塞队列,此队列按照FIFO原则对元素进行排序 |
LinkedBlockingQueue | 链表实现的有界阻塞队列,此队列的默认和最大长度为Integer.MAX_VALUE,此对列按照FIFO原则对元素进行排序 |
LinkedTransferQueue | 链表实现的无界阻塞队列 |
LinkedBlockingDeque | 链表实现的双向阻塞队列 |
PriorityBlockingQueue | 支持优先级排序的无界阻塞队列,默认情况下元素采取自然排序,也可以自定义实现compareTo()方法来指定元素排序规则,或者初始化PriorityBlockingQueue 时,指定构造参数Comparator来对元素进行排序 |
DelayQueue | 优先级队列实现的无界阻塞队列 |
SynchronousQueue | 不存储元素的的阻塞队列,每一个put操作必须等待一个take操作,否则不能继续添加元素 |
阻塞队列的操作方法
在阻塞队列中提供了四种操作方法
插入操作
方法名 | 详述 |
---|---|
add(e) | 添加元素到队列中,如果队列满了,继续插入元素会报IllegalStateException |
offer(e) | 添加元素到队列,同时会返回元素是否插入成功的状态,如果成功返回true |
put(e) | 当阻塞队列满了之后,生成者继续通过put添加元素,队列会一直阻塞生产者线程,直到队列可用 |
offer(e,time,unit) | 当阻塞队列满了之后继续添加元素,生产者线程会被阻塞指定时间,如果超时,则线程直接退出 |
移除操作
方法名 | 详述 |
---|---|
remove() | 当队列为空,调用会返回flase,如果元素移除成功则返回true |
poll() | 当队列存在元素,则从队列中取出一个元素,如果队列为空则返回null |
take() | 基于阻塞方式获取队列中的元素,如果队列中元素为空,则take方法一直阻塞,直到队列中有新的的数据可以消费 |
poll(long timeout, TimeUnit unit) | 带有超时机制的获取数据,如果队列为空,则等待一定时间再去获得元素返回。 |
ArrayBlockingQueue基本原理分析
构造方法
在ABQ中提供了三个构造方法,分别如下
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();
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();
}
}
其中的参数分别是
capacity:队列长度
fair:是否是公平队列,默认非公平。
Collection e: 数据初始化参数
add方法
在add方法中是调用父类方法,在父类方法中主要是添加一个判断,来判断队列是否已经满了,最终还是调用offer方法来进行添加。
public boolean add(E e) {
return super.add(e);
}
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
offer方法
在offer方法中主要做了以下几个事情
1.判断数据是否为空
2.添加重入锁
3.判断队列长度,如果队列长度等于数组长度,放回false
4.队列未满,则调用enqueue将元素添加到队列中。
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
enqueue方法
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x; //通过putIndex直接对数组赋值
if (++putIndex == items.length) //当putIndex==数组长度的时候,赋值为0
putIndex = 0;
count++; //记录队列元素数
notEmpty.signal();//唤醒处于等待状态下的线程,表示当前队列中元素不为空,如果有消费线程在阻塞,就可以开始取出元素。
}
在enqueue方法中,当putIndex==数组长度的时候,会重新赋值为0,这是因为ABQ是一个FIFO队列。
put方法
在put方法于add方法功能相同,差异是put满了会进行阻塞。
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
//这个也是获得锁,但是与lock区别是,
//这个方法优先允许在等待时由其他线程调用interrrupt方法来中断等待,
//直接返回flase,lock是必须获得锁成功后才能响应中断
lock.lockInterruptibly();
try {
while (count == items.length) //队列满了 等待
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
take方法
take方法主要是获得锁,如果队列中没有元素则阻塞,有则调用dequeue。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
dequeue方法
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex]; //获得takeIndex处的元素
items[takeIndex] = null; //删除takeIndex处的元素
if (++takeIndex == items.length) //enqueue逻辑相同
takeIndex = 0;
count--; //队列中元素数自减
if (itrs != null)
itrs.elementDequeued(); //更新迭代器中的元素
notFull.signal();//触发因为队列满了导致被阻塞的队列。
return x;
}
remove方法
移除指定元素的remove(Obj o)方法,主要逻辑也是很简单,就是查询该元素位置,然后再删除
public boolean remove(Object o) {
if (o == null) return false;
final Object[] items = this.items; //获取元素数
final ReentrantLock lock = this.lock;
lock.lock();//获取锁
try {
if (count > 0) {
final int putIndex = this.putIndex; //获取下一个要添加元素时的索引
int i = takeIndex; //获取当前要被移除的元素索引
do {
if (o.equals(items[i])) { //从takeIndex下标开始找到要删除的元素。
removeAt(i); //移除指定元素
return true; //返回结果
}
//当前删除索引执行加1后,判读是否与数组长度相等。
//若为true,说明索引已到数组尽头,将i设置为0
if (++i == items.length)
i = 0;
} while (i != putIndex);//查找到最后一个元素
}
return false;
} finally {
lock.unlock();
}
}