阻塞队列
阻塞队列
阻塞队列的接口声明形式:
public interface BlockingQueue<E>
extends Queue<E>
阻塞队列提供了一下方法:
抛出异常 | 特殊值 | 阻塞 | 超时 | |
---|---|---|---|---|
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove(e) | poll(e) | take(e) | poll(time,unit) |
检查 | element(e) | peek(e) | 不可用 | 不可用 |
put、take方法会产生阻塞,是阻塞队列提供的新的方法阻塞队列特点:
1、集合实例中不能存储null值,否则会抛出空指针或特定的值
2、阻塞队列可以是限定容量,也可以动态扩容
3、阻塞队列是线程安全的集合操作,其内部通过reentrantlock来加锁实现安全
主要实现类
ArrayBlockQueue:有界阻塞队列
private static final long serialVersionUID = -817911632652898426L;
//通过数组保存数据
final Object[] items;
//读数据位置
int takeIndex;
//写数据的位置
int putIndex;
//队列存储的个数
int count;
//锁
final ReentrantLock lock;
//Condition 对象实例进行对象间的通信
private final Condition notEmpty;
private final Condition notFull;
ArrayBlockingQueue的实现是用一个数据来存储所有的数据,而且该数据是不可扩大的数组,另外存储两个索引位置,分别是读操作takelndex和写操作
putindex,另外含有存储数据的数量count属性
支持阻塞需要一个锁Lock和两个条件〔非空、非满)
因为是持有一把锁,所以任何对队列操作只有一个线程,所以索引位置和count数量的操作都是线程安全的
构造函数
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();
}
put方法
public void put(E e) throws InterruptedException {
//检查插入数据是否为null
checkNotNull(e);
final ReentrantLock lock = this.lock;
//中断锁
lock.lockInterruptibly();
try {
//当前集合容量满了,需要进入等待
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
//释放锁
lock.unlock();
}
}
enqueue代码如下:
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
//直接从堆栈取items的引用,更快
final Object[] items = this.items;
//将元素x存放在数组的putIndex位置上
items[putIndex] = x;
//如果插入的位置已经是数组中的最后一个位置了,那么就将putIndex置为0
//因为已经到达最后一个了,那就只能从第一个位置插入元素
if (++putIndex == items.length)
putIndex = 0;
count++;
//唤醒notEmpty队列中等待获取数据的线程
notEmpty.signal();
}
take方法
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
//加锁
lock.lockInterruptibly();
try {
//当容量为空时,则阻塞,等待put方法中的notEmpty.signa的通知
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];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
//通知put可以进行插入
notFull.signal();
return x;
}
LinkedBlockQueue:无界阻塞队列
//容量
private final int capacity;
//记录存储数据的个数
private final AtomicInteger count = new AtomicInteger ( 0 ) ;
//头节点
private transient Node<E> head ;
//下一个节点
private transient Node<E> last;
//创建了一个删除操作锁
private final ReentrantLock takeLock = new ReentrantLock( ) ;
private final Condition notEmpty = takeLock.newCondition ( ) ;
//创建了一个插入的操作锁
private final ReentrantLock putLock = new ReentrantLock( ) ;
private final Condition notFull = putLock.newCondition ( ) ;
1、LinkedBlockingQueue实现上是使用的链表
2、数据存储在Node结构中
3、引入了两把锁,一个入队列锁,一个出队列的锁
put方法
public void put(E e) throws Inter ruptedException {
//插入数据不能为null
if (e == null) throw new NullPointerException();
int c=-1;
Node<E> node = new Node(e) ;
final ReentrantLock putLock = this. putLock;
final AtomicInteger count = this. count;
//添加入队列锁.
putLock. lockInterruptibly();
try {
//当容器满时,当前通知存在两个点会通知
//1、当容量不满时,由put的线程通知
//2、当容量满时,由take的线程通知
while (count.get() == capacity) {
notFull. await() ;
}
//将节点插入到链表尾部
enqueue(node);
//原子性的++操作
c = count.getAndIncrement();
//通知take出队列的notFull. signal操作
if (c + 1 < capacity)
notFull.signal();
} finally {
//释放入队列锁
putLock. unlock();
}
if(c==0)
signalNotEmpty();
}
SychronousQueue:同步阻塞队列
同步队列,每一个插入操作必须等待两一个线程的移除操作,同样,一个线程的移除操作必须等待另一个线程的插入操作元素是不会做停留的