BlockingQueue(阻塞队列)应用场景:
BlockingQueue即阻塞队列是一种将ReentrantLock应用比较精彩的一种表现,它最经典的应用就是实现生产者与消费者模式 ;
BlockingQueue接口特点:
- 不接受null元素,当添加一个null元素时,会抛出NullPointerException异常
- 可以限定容量
- 实现主要用于生产者-消费者队列
- 线程安全的,所有排队方法都可以使用内部所或者其他形式并发控制来达到自动达到目的
阻塞队列与非阻塞队列最大的区别就是:
阻塞队列能够阻塞当前试图从队列获取元素的线程,而非阻塞队列不会。因此在面对生产者-消费者 模型时,使用非阻塞队列就必须额外地实现同步策略以及线程间唤醒策略,这个实现起来就比较麻烦。阻塞队列就不用担心这些问题,它会对当前线程产生阻塞,比如一个线程从一个空的阻塞队列中取元素,此时线程会被阻塞住直到队列中有元素。当队列中有元素后,被阻塞线程会自动被唤醒。
阻塞队列和非阻塞队列中常用方法对比:
非阻塞队列常用方法:
对于非阻塞队列,一般建议使用offer,poll,peek三个方法。因为这三个方法可以通过返回值判断操作成功与否,而使用add和remove方法需要捕获异常才能判断操作是否成功。此外,要注意这些方法都没有进行同步处理。
常用方法 | 解释 |
---|---|
add(E e) | 将元素e插入到队列末尾,如果插入成功,返回true;插入失败(队列已满),抛出异常; |
offer(E e) | 将元素e插入到队尾,如果插入成功返回true;插入失败(队列已满),返回false; |
remove() | 移除队首元素,成功返回true;移除失败(队列为空),抛出异常; |
poll() | 移除并获取队首元素,若成功,则返回队首元素;否则,返回null; |
peek() | 获取队首元素,但不移除。若成功,则返回队首元素;否则返回null; |
阻塞队列常用方法:BlockingQueue也实现了Queue,因此也具有以上方法,并且进行了同步处理
常用方法 | 解释 |
---|---|
put(E e) | 向队尾存入元素,如果队列满,则等待; |
take() | 从队首取元素,如果队列为空,则等待; |
offer(E e ,long timeOut,TimeUnit unit) | 向队尾插入元素,如果队列满,则等待一段时间, 当时间到达时,如果还没有插入成功,则返回false; 否则返回true; |
poll(long timeout , TimeUnit unit ) | 从队首取元素,如果队列为空,则等待一段时间, 当时间期限达到时,如果取不到,则返回null; 否则返回取得的元素; |
BlockingQueue提供的常用方法的区别
抛出异常 | 特殊值 | 阻塞 | 超时等待 | |
---|---|---|---|---|
插入 | add(E e) | offer(E e) | put(E e) | offer(E e,long timeout,TimeUnit unit) |
移除 | remove() | poll() | take() | poll(long timeout,TimeUnit unit) |
检查 | element() | peek() | 不可用 | 不可用 |
BlockingQueue接口实现类:
- ArrayBlockingQueue(有界阻塞队列) : 基于数组实现一个阻塞队列,在创建ArrayBlockingQueue对象时必须指定容量大小。并且可以指定公平性和非公平性 ,默认情况为非公平的,即不保证等待时间最长的线程最先能够访问队列。
- LinkedBlockingQueue(无界阻塞队列): 基于链表实现的一个阻塞队列,在创建LinkedBlokingQueue对象时,如果不指定容量大小,默认为Interger.MAX_VALUE。
- PriorityBlockingQueue(无界优先级阻塞队列): 数据结构为数组,他会按照元素的优先级对元素进行排序,按照优先级顺序出队,每次出队的元素都是优先级最高的元素;
- DelayQueue(基于PriorityQueue实现的延迟队列,是一个无界的阻塞队列) ,用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。因此向队列中插入时永远不会阻塞(因为无界),获取时才有可能被阻塞。
- SynchronousQueue(同步阻塞队列): 队列大小为1,一个元素要放到该队列必须有一个线程在获取元素。
- DelayedWorkQueue: 该队列为ScheduledThreadPoolExecutor中的静态内部类,ScheduledThreadPoolExecutor便是通过该队列使得队列中的元素按一定顺序排列从而使延迟任务和周期性任务 顺利执行。
- BlockingQueue(接口): 双向阻塞队列的接口。
- TransferQueue(接口): 定义了另一种阻塞情况:生产者会一直阻塞直到所添加到队列的元素被某个消费者所消费,而BlockingQueue只需将元素添加到队列中后生产者便会停止被阻塞。
ArrayBlockingQueue源码详解:
特点:ArrayBlockingQueue只同时允许一个线程对其进行操作;
继承关系:
public class ArrayBlockingQueue<E>
extends AbstractQueue<E> // 继承了队列常用的方法,
implements BlockingQueue<E>, //实现了阻塞队列
java.io.Serializable { //可被序列化
基本属性:
final Object[] items; //数组,ArrayBlockingQueues底层数据结构是数组
int takeIndex; //读取操作的位置
//写操作的位置
int putIndex;
//队列里面元素的个数
int count;
final ReentrantLock lock; //重入锁
private final Condition notEmpty; //不为空时的锁对象
private final Condition notFull; //不为满时的锁对象
构造函数:
//指定大小的队列
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(); //解锁
}
}
put方法:插入元素,若满,则等待;
public void put(E e) throws InterruptedException {
checkNotNull(e); //阻塞队列不能存储null值
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); //添加可中断锁
try {
while (count == items.length) //数组已满则处于await()等待状态
//take操作中会给notFull发送消息解除阻塞状态
notFull.await(); // 通过已满同步条件,使当前线程阻塞
enqueue(e); //未阻塞,添加元素
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
final Object[] items = this.items;
items[putIndex] = x; //将元素添加进数组
if (++putIndex == items.length) //下一次添加元素位置,表明数组是循环使用的
putIndex = 0;
count++; //队列元素数量+1
notEmpty.signal(); //唤醒一个等待输出元素的线程
//作用于take操作下,唤醒notEmpty()阻塞队列中数组为空时被阻塞的线程中任意一个
}
tack方法:取出元素,若空,则阻塞;
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); //可中断锁
try {
while (count == 0) //数组中无元素
notEmpty.await(); //通过已空条件对象,使线程阻塞。
return dequeue(); //未空,删除元素
} finally {
lock.unlock();//解锁
}
}
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex]; //得到当前需要take的元素
items[takeIndex] = null; //清空元素
if (++takeIndex == items.length) //下一次删除元素角标
takeIndex = 0;
count--; //队列元素--
if (itrs != null)
itrs.elementDequeued();
notFull.signal(); //唤醒notFull阻塞队列中准备添加元素的线程
return x;
}
LinkedBlockingQueue源码详解:
特点:可以并发对链表队列进行修改,因为提供了两把锁,一把插入锁,一把读取锁。
注意:生产者的条件对象线程只能在插入锁里等待/唤醒;消费者同理;
这也是LickedBlockingQueue的put和take方法比多一个方法的原因。
继承关系:
public class LinkedBlockingQueue<E>
extends AbstractQueue<E> //继承了队列相关操作
implements BlockingQueue<E>, //实现阻塞对象相关操作;
java.io.Serializable { //可被序列化
基本属性:
static class Node<E> { //数据节点
E item; //存储数据
Node<E> next; //下一个节点
Node(E x) { item = x; } //存储数据
}
private final int capacity; //队列容量
private final AtomicInteger count = new AtomicInteger();
//原子性的对队列长度操作
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(); //输入条件对象
继承关系:
//创建一个容量未Integer_MAx_VALUE的队列
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
//给容量队列
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null); //初始化头节点和尾结点
}
// 创建一个最大容量的队列并且最初包含collection集合元素
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
final ReentrantLock putLock = this.putLock; //得到插入锁
putLock.lock(); //加锁
try {
int n = 0;
for (E e : c) { //迭代遍历集合
if (e == null) //元素不能未null
throw new NullPointerException();
if (n == capacity) //超出队列个数范围
throw new IllegalStateException("Queue full");
enqueue(new Node<E>(e)); //插入元素
++n;
}
count.set(n); //CAS更新队列元素个数
} finally {
putLock.unlock(); //解锁
}
}
put方法:
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException(); //不允许null值
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock; //得到输入锁
final AtomicInteger count = this.count; //元素个数
putLock.lockInterruptibly(); //加可被中断的锁
try {
while (count.get() == capacity) {
//达到队列最大值,输入对象条件线程进行等待
notFull.await();
}
enqueue(node); //将此元素添加到链表末尾
c = count.getAndIncrement(); //CAS进行元素数量++操作 //先返回在++
if (c + 1 < capacity)// 当前元素数量+1小于最大容量,表示还可以添加元素
notFull.signal(); //唤醒等待添加元素的线程
} finally {
putLock.unlock();
}
//c==0 实际 此时count==1
if (c == 0) //唤醒阻塞的输出线程,队列中只有一个元素,唤醒一个输出线程
signalNotEmpty();
}
//将新节点添加到链表队尾
private void enqueue(Node<E> node) {
last = last.next = node;
}
//唤醒一个消费者线程
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
take方法:
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) { //无元素,获取元素线程进行阻塞
notEmpty.await();
}
x = dequeue(); //删除并得到此元素
c = count.getAndDecrement(); //先获得值在--
if (c > 1)
notEmpty.signal(); //队列中还有元素,唤醒消费者线程进行消费
} finally {
takeLock.unlock();
}
//c此时实际为capacity-1,因为减一操作后返回的是原值而不是新值
if (c == capacity) //队列差一个元素就满了,唤醒一个生产者线程进行生产;
signalNotFull();
return x;
}
//从头部获取元素,并产生新的头部;
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
// assert head.item == null;
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first; //新的头节点
E x = first.item; //得到此节点数据
first.item = null; //将数据置为null,头节点元素都是置为null的
return x;
}
//唤醒一个生产者线程
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}