概要
阻塞队列(BlockingQueue)是由 java.util.concurrent 提供的用于解决并发生产者-消费者最常用的类,特点是在任意时刻只有一个线程可以执行存/取操作.
它额外支持在检索元素时等待队列变为非空,并在存储元素时等待队列中的空间变为可用的操作。
BlockingQueue方法有四种形式,它们有不同的方法来处理不能立即满足的操作,但可能在将来的某个时候得到满足:一种方法抛出异常,第二种方法返回一个特殊值(null或false,取决于操作),第三个无限期地阻塞当前线程,直到操作可以成功,而第四个阻塞只在给定的最大时间限制后放弃。这些方法总结如下:
队列类型
- 无限队列 (unbounded queue ) - 可以无限增长(受内存大小限制)。
- 有限队列( bounded queue)- 定义了最大容量
队列数据接口
- 队列一般由链表或者数组来实现
- 一般而言具备先FIFO特性
常见的4中阻塞度列
-
ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
-
LinkedBlockingQueue :一个由链表结构组成的有界阻塞队列。
-
PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
-
DelayQueue:一个使用优先级队列实现的无界阻塞队列。
ArrayBlockingQueue
一个由数组结构组成的有界阻塞队列,它遵循FIFO(first-in-first-out)原则,最先进入队列的成员为队列的头部,最晚进入队列的元素为尾部,每次执行插入操作是都把元素插入到队列的尾部,队列的检索操作是从队列的头部开始的.
队列的容量一旦创建就不能修改了,当向满容量的队列执行put操作的时候会导致线程阻塞,当向一个空队列执行take操作的时候也会导致线程阻塞.
成员变量
//下面为主要的成员变量
final Object[] items; //队列容器
int count;//队列中元素的个数
/** 独占锁,保证队列的存取操作始终只能由一个线程执行
*默认为非公平锁,可以在队列创建的时候指定公平/非公平锁
*改锁是以AQS中CLH队列为基础的,获取到锁的线程可以继续执行,获取不到锁的线程会被阻塞并保存到CLH队列中,知道锁被释放后被唤醒,继续去争抢锁
*/
final ReentrantLock lock;
/** Condition for waiting takes
Condition 为一个条件队列其子类为AbstractQueuedSynchronizer 中的内部类ConditionObject
ConditionObject 是一个由AbstractQueuedSynchronizer中内部类Node组成的单项链表-条件队列,
当阻塞队列empty的时候,执行take操作的线程(消费者)会执行notEmpty.await()阻塞并存入到notEmpty条件队
列中,等待执行put操作的线程(生产者)执行notEmpty.signal(); 发送信号,收到信号的消费者会从
notEmpty条件队列移入CLH队列中去等待独占锁释放后争抢锁
*/
private final Condition notEmpty;
/** Condition for waiting puts
*当阻塞队列full(count == items.length) 的时候生产者会执行 notFull.await() 线程阻塞并存入到
notFull条件队列中,等待消费者执行notFull.signal()发送型号,接受到信号的生产者会从notFull条件队列
中移入到CLH队列中等待独占锁释放后争抢锁.
*/
private final Condition notFull;
构造方法
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
//初始化队列容量,一旦创建不能修改
this.items = new Object[capacity];
//初始化独占锁,默认为公平锁 也可以通过fair指定为非公平
lock = new ReentrantLock(fair);
//初始化 notEmpty 条件队列
notEmpty = lock.newCondition();
//初始化 notFull 条件队列
notFull = lock.newCondition();
}
put(E e)操作
//插入到队列尾部
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();//可中断锁,当线程中断后抛异常
try {
// 当队列满的时候线程会被阻塞并存入到notFull条件队列中等待消费者发信号被唤醒
while (count == items.length)
notFull.await();/
enqueue(e); //入队
} finally {
lock.unlock();
}
}
// 入队操作 , 并发送信号给消费者,告诉消费者队列有元素可取了
//注意,只有拿到独占锁的线程才可以执行该操作
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();//给消费者发信号
}
take()操作
//按照FIFO的原则 向外拿元素
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
//当队列为空的时候消费者线程阻塞并存入到notEmpty条件队列中,等待notFull发送信号
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
//出队 并发送信号给生产者线程,唤醒生产者向阻塞队列执行put操作
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();
notFull.signal(); //发送信号
return x;
}
原理图示
由于图片大小问题,可能导致显示不清楚,大家可以加我v索要原理图示