并发编程之阻塞队列
一、基础概念
1.1 生产者消费者
生产者消费者其实是一种设计模式,它是为了解决生产者和消费者之间的强耦合问题,为了解决这个问题,我们通常会选择引入一个中间容器(队列),生产者不直接和消费者打交道,它会把消息扔到中间容器中,而消费者需要自己去中间容器内拿到消息进行处理。像我们在项目中常使用的RocketMQ、RabbitMQ就是基于生产者消费者来实现的。
1.2 JUC阻塞队列的存取方法

常用的存储方法:
add(E) //往队列中添加数据,如果队列满了,抛出异常
offer(E) //往队列中添加数据,如果队列满了,返回false
offer(E,timeout,unit) //往队列中添加数据,如果队列满了,等待timeout,超过时间还没放进去,返回false
put(E) //往队列中添加数据,如果队列满了,挂起线程,等到有位置放进去
常用的获取方法:
remove() //从队列中获取数据,如果队列为空,抛出异常
poll() //从队列中获取数据,如果队列为空,返回null
poll(timeout,unit) //从队列中获取数据,如果队列为空,等待timeout,超过时间还是没有,返回null
take() //从队列中获取数据,如果队列为空,挂起等待,等到有数据获取
二、ArrayBlockingQueue
2.1 基础使用
ArrayBlockingQueue是基于数组实现的队列,所以我们在创建实例的时候必须指定数组的长度。
public class Code01_ArrayBlockingQueue {
public static void main(String[] args) throws InterruptedException {
//必须指定数组的长度
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(4);
//生产者生产数据
queue.add(1);
queue.offer(2);
queue.offer(3,2, TimeUnit.SECONDS);
queue.put(4);
//消费者消费数据
System.out.println(queue.remove());
System.out.println(queue.poll());
System.out.println(queue.poll(2, TimeUnit.SECONDS));
System.out.println(queue.take());
}
}
2.2 核心成员变量
//存储元素的队列
final Object[] items;
//消费者获取元素的下标
int takeIndex;
//生产者存储元素的下标
int putIndex;
//队列中存储的元素个数
int count;
//阻塞队列用于并发控制的锁
final ReentrantLock lock;
//用于存放获取不到数据而挂起的消费者
private final Condition notEmpty;
//用于存放存储不进数据而挂起的生产者
private final Condition notFull;
2.3 生产者方法分析
2.3.1 add方法
public boolean add(E e) {
return super.add(e);
}
public boolean add(E e) {
//这里调用的还是ArrayBlockingQueue里面的offer方法
if (offer(e))
//添加成功,返回true
return true;
else
//添加失败,抛异常
throw new IllegalStateException("Queue full");
}
2.3.2 offer方法
public boolean offer(E e) {
//检查是否为空
checkNotNull(e);
//拿到队列的lock锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
//如果队列已经满了,返回false
return false;
else {
//如果没满,入队
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
//拿到队列
final Object[] items = this.items;
//将元素放进队列
items[putIndex] = x;
if (++putIndex == items.length)
//如果++putIndex等于队列长度,表示队列满了,putIndex归0
putIndex = 0;
//元素数量++
count++;
//唤醒在Condition队列中等待的消费者
notEmpty.signal();
}
2.3.3 offer(timeout,unit)
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
//检查元素是否为空
checkNotNull(e);
//将等待时间转成纳秒
long nanos = unit.toNanos(timeout);
//拿到队列的lock锁
final ReentrantLock lock = this.lock;
//加锁,可中断
lock.lockInterruptibly();
try {
//循环判断队列是否是满的状态
while (count == items.length) {
if (nanos <= 0)
//如果队列已满,并且等待时间不够了,返回false
return false;
//挂起当前线程指定时间到notFull对应的Condition队列,返回的nanos是剩余时间(被唤醒了)
nanos = notFull.awaitNanos(nanos);
}
//队列未满,入队
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
2.3.4 put方法
public void put(E e) throws InterruptedException {
//检查是否为空
checkNotNull(e);
//拿到当前队列的lock锁
final ReentrantLock lock = this.lock;
//加锁
lock.lockInterruptibly();
try {
while (count == items.length)
//如果队列已满,就挂起等待唤醒
notFull.await();
//队列未满,入队
enqueue(e);
} finally {
lock.unlock();
}
}
2.4 消费者方法分析
2.4.1 remove方法
public E remove() {
//调用poll方法难道队列元素
E x = poll();
if (x != null)
//不为空返回元素
return x;
else
//为空则抛出异常
throw new NoSuchElementException();
}
2.4.2 poll方法
public E poll() {
//拿到队列的lock锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//如果队列为空,返回null,否则出队
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
//拿到队列
final Object[] items = this.items;
//从队列中取出takeIndex下标对应元素
E x = (E) items[takeIndex];
//将该位置元素置为空
items[takeIndex] = null;
if (++takeIndex == items.length)
//如果队列元素拿光了,takeIndex归0
takeIndex = 0;
//元素数量--
count--;
//itrs暂且不看。。。
if (itrs != null)
itrs.elementDequeued();
//唤醒notFull里面挂起的生产者
notFull.signal();
return x;
}
2.4.3 poll(timeout,unit)
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
//将等待时间转纳秒
long nanos = unit.toNanos(timeout);
//拿到队列的lock锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
//如果剩余等待时间不足,返回null
return null;
//队列没数据,挂起当前消费者等待唤醒
nanos = notEmpty.awaitNanos(nanos);
}
//出队
return dequeue();
} finally {
lock.unlock();
}
}
2.4.4 take方法
public E take() throws InterruptedException {
//拿到队列的lock锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
//没数据就挂起
notEmpty.await();
//有数据执行出队
return dequeue();
} finally {
lock.unlock();
}
}
2.5 虚假唤醒问题
先看下面两段代码:
//逻辑1
if(count == 0){
notEmpty.await();
}
return dequeue();
//逻辑2
while(count == 0){
notEmpty.await();
}
return dequeue();
假设现在有一个消费者线程A来队列中拿数据,但此时队列为空,它只能选择挂起等待被唤醒;接着来了一个生产者线程B,它拿到lock锁,向队列中存放数据,然后将正在等待的A线程唤醒了,于是线程A准备去拿锁了,但是好巧不巧,刚好有个消费者线程C先它一步拿到了lock锁,于是它把刚刚线程B存的数据拿走了,接着A拿到了锁,如果是执行的逻辑1的代码,那么它就会执行出队操作,但此时队列的数据已经被拿走了,队列是空的,这就是虚假唤醒。我们前面不管是往队列中存还是取数据,都会加上while循环而不是if判断,就是为了避免出现虚假唤醒问题。
三、LinkedBlockingQueue
3.1 底层实现
LinkedBlockingQueue底层是基于链表结构实现的,链表节点封装为一个Node对象存储。
//LinkedBlockingQueue存储数据的载体
static class Node<E> {
E item;//数据
Node<E> next;//下一个节点
Node(E x) { item = x; }
}
3.2 构造方法与核心成员变量
//无参构造
public LinkedBlockingQueue() {
//默认链表最大长度为Integer.MAX_VALUE
this(Integer.MAX_VALUE);
}
//指定长度的构造
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
//链表的head头节点和last尾节点是两个伪节点
last = head = new Node<E>(null);
}
//链表最大长度
private final int capacity;
//当前链表长度,即当前有多少个链表节点
private final AtomicInteger count = new AtomicInteger();
//head头节点,用于获取数据
transient Node<E> head;
//last尾节点,用于存放数据
private transient Node<E> last;
//获取数据的lock锁
private final ReentrantLock takeLock = new ReentrantLock();
//获取数据时链表为空,挂起线程的Condition
private final Condition notEmpty = takeLock.newCondition();
//存放数据的lock锁
private final ReentrantLock putLock = new ReentrantLock();
//存放数据时链表已满,挂起线程的Condition
private final Condition notFull = putLock.newCondition();
3.3 生产者方法分析
3.3.1 add方法
public boolean add(E e) {
//调用的offer方法
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
3.3.2 offer方法
public boolean offer(E e) {
//如果添加数据为空,抛异常
if (e == null) throw new NullPointerException();
//拿到当前链表节点个数
final AtomicInteger count = this.count;
//如果当前链表节点个数等于capacity,说明满了,返回false
if (count.get() == capacity)
return false;
int c = -1;
//将数据封装为Node对象
Node<E> node = new Node<E>(e);
//拿到putLock锁
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
//判断当前节点个数有没有超出限制,前面判断的时候还没有拿到锁
//这个和synchronized那个双重检验锁很像
if (count.get() < capacity) {
//执行入队操作
enqueue(node);
//注意这里是先get再++,也就是说c获得的是count的旧值
c = count.getAndIncrement();
//其实就是count是否小于capacity,如果是,就唤醒其它的生产者,告诉它们有空位可以存数据
//为什么这里不像ArrayBlockingQueue一样让消费者来唤醒而是要自唤醒呢,因为LinkedBlockingQueue
//的读锁和写锁是分离的,消费者想要唤醒生产者必须要拿到写锁才行,而我们这里已经是持有锁状态了
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
//c为0表示之前队列是空的,有可能有消费者线程正在挂起,我们需要把它们唤醒
signalNotEmpty();
return c >= 0;
}
private void enqueue(Node<E> node) {
//先将last.next指向当前node,然后再将last指向node
last = last.next = node;
}
private void signalNotEmpty() {
//拿到读锁
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
//唤醒正在挂起的消费者
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
3.3.3 offer(timeout,unit)
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
//如果元素为空抛异常
if (e == null) throw new NullPointerException();
//等待时间转纳秒
long nanos = unit.toNanos(timeout);
int c = -1;
//拿到写锁和count
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly();
try {
while (count.get() == capacity) {//是否已满
if (nanos <= 0)
return false;
//挂起线程
nanos = notFull.awaitNanos(nanos);
}
//入队
enqueue(new Node<E>(e));
c = count.getAndIncrement();
if (c + 1 < capacity)
//还有位置,唤醒其它生产者
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
//唤醒消费者
signalNotEmpty();
return true;
}
3.3.4 put方法
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
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();
if (c + 1 < capacity)
notFull.signal();
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
}
3.4 消费者方法分析
3.4.1 remove方法
public E remove() {
E x = poll();
if (x != null)
return x;
else
throw new NoSuchElementException();
}
3.4.2 poll方法
public E poll() {
//拿到count
final AtomicInteger count = this.count;
if (count.get() == 0)
//没数据,返回null
return null;
E x = null;
int c = -1;
//拿到读锁
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() > 0) {
//如果count>0,才能执行出队操作
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
//还有数据呢,唤醒其它消费者来拿
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
//消费者把数据拿了,唤醒生产者继续生产
signalNotFull();
return x;
}
private E dequeue() {
Node<E> h = head;
//拿到head的下一个节点
Node<E> first = h.next;
//头节点next指向自己,没有node指向自己,也不指向别的节点,帮助GC
h.next = h; // help GC
//head指向first,原来的head被GC了,first成为新的head
head = first;
//拿到first里面的数据
E x = first.item;
//清空数据
first.item = null;
//返回
return x;
}
3.4.3 poll(timeout,unit)
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E x = null;
int c = -1;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
3.4.4 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();
}
if (c == capacity)
signalNotFull();
return x;
}

浙公网安备 33010602011771号