阻塞队列
七种阻塞队列
ArrayBlockingQueue
数组实现的有界阻塞队列,此队列按照先进先出FIFO原则对元素进行排序
LinkedBlockingQueue
链表实现的有界阻塞队列,此队列的默认和最大长度为Integer.MAX_VALUE。此队列按照先进先出的原则
PriorityBlockingQueue
支持优先级排序的无界阻塞队列,默认情况下元素采用自然排序升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则,或者初始化PriorityBlockingQueue时,指定构造参数Comparetor来对元素进行排序
DelayQueue
优先级队列实现的无界阻塞队列
SynchronousQueue
不存储元素的阻塞队列,每一个put操作必须必须等待一个tack操作,否则不能继续添加元素
LinkedTransferQueue
链表实现的无界阻塞队列
LinkedBlockingDeque
链表实现的双向阻塞队列
阻塞队列的操作方法
阻塞队列中提供四种处理方式
插入操作
- add(e) : 添加元素到队列中,如果队列满了,继续插入元素就会报错,IllgalStateException
- offer(e): 添加元素到队列,即时返回插入成功的状态,如果成功则返回true
- put(e): 当阻塞队列满了之后,生产者继续通过put添加元素,队列会一直阻塞生产者线程,直到可用
- offer(e,time,unit):当阻塞队列满了之后继续添加元素,生产者线程会被阻塞指定时间,如果超时,线程直接退出
移除操作
- remove() :当队列为空时,调用remove会返回false,如果元素移除成功,则返回true
- poll(): 当队列存在元素时,则取出,如果不存在则返回null
- take(): 基于阻塞的方式从队列中取出元素,如果队列为空,则take方法会一直阻塞,知道队列中有新的数据可以消费
- poll(time, unit) : 带超时机制的获取数据,如果队列为空则会等待指定的时间再去获取元素返回。‘
ArrayBlockingQueue原理分析
capacity:表示数组的长度,也就是队列的长度
fair:表示是否为公平的阻塞队列,默认情况下构造的是非公平的阻塞队列。
其中第三个构造方法就不解释了,它提供了接收一个几个作为数据初始化的方法
ArrayBlockingQueue内部的阻塞队列是通过重入锁ReenterLock和Condition条件队列实现的,所以ArrayBlockingQueue中的元素存在公平访问与非公平访问的区别,对于公平访问队列,被阻塞的线程可以按照阻塞的先后顺序访问队列,即先阻塞的线程先访问队列。而非公平队列,当队列可用时,阻塞的线程将进入争夺访问资源的竞争中,也就是说谁先抢到谁就执行,没有固定的先后顺序
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();//初始化非满等待队列
}
-
Add方法
以add方法作为入口,在add方法中会调用父类的add方法,也就是AbstractQueue.如果看源码看得比较多的话,一般这种写法都是调用父类的模版方法来解决通用性问题
public boolean add(E e) {
return super.add(e);
}
从父类的add方法可以看到,这里做了一个队列是否满了的判断,如果队列满了直接抛出一个异常
public boolean add(E e) {
if (offer(e))
return true;
else throw new IllegalStateException("Queue full");}
- offer方法
add方法最终还是调用offer方法来添加数据,返回一个添加成功或者失败的布尔值反馈
这段代码做了几个事情- .判断添加的数据是否为空
- 添加重入锁
- 判断队列长度,如果队列长度等于数组长度,表示满了直接返回false
- 否则,直接调用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
这个是最核心的逻辑,方法内部通过putIndex索引直接将元素添加到数组items
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等于数组长度时,将putIndex重置为0
putIndex = 0;
count++; //记录队列元素的个数
notEmpty.signal(); // **唤醒处于等待状态下的线程,表示当前队列中的元素不为空,如果存在消费者线程阻塞,就可以开始取出元素**
}
- put方法
put方法和add方法功能一样,差异是put方法如果队列满了,会阻塞。这个在最开始的时候说过。接下来看一下它的实现逻辑
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();// //这个也是获得锁,但是和lock的区别是,这个方法优先允许在等待时由其他线程调用等待线程的interrupt方法来中断等待直接返回。而lock方法是尝试获得锁成功后才响应中断
try {
while (count == items.length)
notFull.await();//队列满了的情况下,当前线程将会被notFull条件对象挂起加到等待队列中
// 当其他线程调用了condition的signal方法后,condition状态的节点会从等待队列转移到同步队列中,等待获取同步锁。
enqueue(e);
} finally {
lock.unlock();
}
}
- take方法
take方法是一种阻塞获取队列中元素的方法
它的实现原理很简单,有就删除没有就阻塞,注意这个阻塞是可以中断的,如果队列没有数据那么就加入notEmpty条件队列等待(有数据就直接取走,方法结束),如果有新的put线程添加了数据,那么put操作将会唤醒take线程,执行take操作。
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await(); //如果队列为空的情况下,直接通过await方法阻塞
return dequeue();
} finally {
lock.unlock();
}
}
如果队列中添加了元素,那么这个时候,会在enqueue中调用notempty.signal唤醒take线程来获得元素
dequeue方法
这个是出队列的方法,主要是删除队列头部的元素并发返回给客户端
takeIndex,是用来记录拿数据的索引值
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];//默认获取0位置的元素
items[takeIndex] = null;//将该位置的元素设置为空
if (++takeIndex == items.length)//这里的作用也是一样,如果拿到数组的最大值,那么重置为0,继续从头部位置开始获取数据
takeIndex = 0;
count--;//记录元素个数递减
if (itrs != null)
itrs.elementDequeued(); //同时更新迭代器中的元素数据
notFull.signal(); //触发因为队列满了以后导致的被阻塞的线程
element 和peek不会移除元素,poll会移除元素