1 基础概念
1.1 生产者消费者概念
生产者消费者是设计模式的一种,让生产者和消费者基于一个容器来解决强耦合问题。
这种容器最常用的结构就是队列结构
1.2 JUC阻塞队列中常用的存取方法
常用的方法都来于JUC包下的BlockingQueue
生产者存储方法
add(E) //添加数据到队列中,如果队列满了无法存储,此时抛出异常
offer(E) //添加数据到队列,如果队列满了,直接返回false
offer(E,timeout,unit) // 添加数据到队列,如果队列满了,则阻塞timeout时间,如果等待一定时间还未成功,则返回false
put(E) // 添加数据到队列中,如果队列满了,则挂起线程,等到队列中有空间再扔进去
消费者取数据
remove(E) //从队列中移除数据,先进先出FIFO,如果队列为null 则抛出异常
poll() // 从队列中移除数据,如果队列为空,返回null
poll(timeout,unit) // 从队列中移除数据,如果队列为null,则挂起线程等待时间,
peek() // 移除数据,如果为null,则死等
1.3 ArrayBlockingQueue的基本用法
ArrayBlockingQueue是基于数组实现的队列,数组长度不可变,在初始化时必须制定数组长度。
=========================生产者的实现原理======================
public boolean add(E e) {
return super.add(e);
}
public boolean add(E e) {
if (offer(e))
return true;
else
// offer返回false 直接抛出异常
throw new IllegalStateException("Queue full");
}
public boolean offer(E e) {
Objects.requireNonNull(e);
// 使用ReentrantLock锁
final ReentrantLock lock = this.lock;
//保证线程安全 直接加锁
lock.lock();
try {
// 如果当前队列的长度等于队列的数组长度,返回false
if (count == items.length)
return false;
else {
// 队列没有满,则放入队列,返回true
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
private void enqueue(E e) {
// 拿到数组的引用
final Object[] items = this.items;
//putIndex 存储数据的下标
items[putIndex] = e;
// 对putIndex进行++操作,并判断是否等于队列的数组长度,则索引设置为0
if (++putIndex == items.length) putIndex = 0;
// 添加成功,队列中的元素个数++
count++;
// 将Condition中阻塞的线程唤醒,notEmpty消费者挂起以及唤醒使用;notFull生产者挂起以及唤醒使用
notEmpty.signal();
}
offer(E,timeout,unit)方法原理分析
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
Objects.requireNonNull(e);
// 将时间单位转换为纳秒
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
// 允许线程中断并抛出异常的加锁方式
lock.lockInterruptibly();
try {
// 设置循环体结束条件,当count等于队列数组长度的时候,说明队列满了,跳出循环,使用while虚假唤醒
while (count == items.length) {
// 是否还有等待的时间,不充裕直接false
if (nanos <= 0L)
return false;
// 生产者将线程挂起,并调用Condition的awaitNanos方法,nanos的值被刷新为剩余的等待时间
// 恢复执行时,重新获取锁资源
nanos = notFull.awaitNanos(nanos);
}
// 将数据扔到阻塞队列中
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
// 此方法查看《锁》的博客详情
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
final long deadline = System.nanoTime() + nanosTimeout;
long initialNanos = nanosTimeout;
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
if (nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
long remaining = deadline - System.nanoTime(); // avoid overflow
return (remaining <= initialNanos) ? remaining : Long.MIN_VALUE;
}
put() 方法原理分析
public void put(E e) throws InterruptedException {
Objects.requireNonNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
// 一直阻塞挂起线程等待
notFull.await();
// 放到阻塞队列中
enqueue(e);
} finally {
lock.unlock();
}
}
=======================消费者方法实现原理======================
remove()实现原理分析
public E remove() {
E x = poll();
// 有数据返回数据,否则抛出异常
if (x != null)
return x;
else
throw new NoSuchElementException();
}
public E poll() {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 如果队列为null,返回null,否则调用dequeue方法
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
// 将成员变量引用为局部变量
final Object[] items = this.items;
@SuppressWarnings("unchecked")
// 获取指定索引位置的数据
E e = (E) items[takeIndex];
// 将数组上指定索引位置设为null
items[takeIndex] = null;
// 设置下次取数据时的索引位置
if (++takeIndex == items.length) takeIndex = 0;
count--;
// 迭代器
if (itrs != null)
itrs.elementDequeued();
// 唤醒当前Condition中排队的一个node
notFull.signal();
return e;
}
poll(long timeout, TimeUnit unit) 原理分析
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0L)
// 没数据 返回null
return null;
// 没有数据,挂起消费者线程
nanos = notEmpty.awaitNanos(nanos);
}
// 有数据,取出数据返回
return dequeue();
} finally {
lock.unlock();
}
}
take() 原理分析
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 虚假唤醒
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
虚假唤醒??
组设队列中,如果需要线程挂起操作,判断有无数据的位置采用while循环,不能换成if判断,在多线程场景中,会出现数据安全问题,因此必须使用while循环的虚假唤醒,时时进行循环体条件的判断
1.4 LinkedBlockingDeque 的实现原理分析
LinkedBlockingDeque是基于链表实现的阻塞队列
// 链表头
transient Node<E> first;
// 链表尾
transient Node<E> last;
// 链表的长度
private transient int count;
// 链表的初始化大小
private final int capacity;
final ReentrantLock lock = new ReentrantLock();
// 消费者的挂起操作以及唤醒Condition
private final Condition notEmpty = lock.newCondition();
// 生产者的挂起操作以及唤醒Condition
private final Condition notFull = lock.newCondition();
add()的实现原理分析
public boolean add(E e) {
addLast(e);
return true;
}
public void addLast(E e) {
// 返回false 抛出异常
if (!offerLast(e))
throw new IllegalStateException("Deque full");
}
public boolean offerLast(E e) {
//非空校验
if (e == null) throw new NullPointerException();
//构造一个node节点对象
Node<E> node = new Node<E>(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
//添加到链表中
return linkLast(node);
} finally {
lock.unlock();
}
}
// false 添加失败;true添加成功
private boolean linkLast(Node<E> node) {
// 如果链表长度超过初始长度,直接返回false,添加失败
if (count >= capacity)
return false;
//将成员变量指向局部变量,获取尾节点
Node<E> l = last;
// 将node节点的前置指针指向当前链表的尾节点
node.prev = l;
// 将当前node设置为尾节点
last = node;
// 如果头节点不存在,则设置头节点为当前node
if (first == null)
first = node;
else
//否则设置为节点的后置指针指向当前node
l.next = node;
++count;
// 唤醒消费者
notEmpty.signal();
return true;
}
消费者原理的核心
// 从链表中去掉头节点,头节点为哨兵节点(虚拟节点)
private E unlinkFirst() {
// 将成员变量指向局部变量 获取链表头节点
Node<E> f = first;
// 如果头部为null 直接返回null
if (f == null)
return null;
// 获取头节点的下一个指针
Node<E> n = f.next;
// 将成员变量指向局部变量 获取链表的item
E item = f.item;
// 清空头节点的item
f.item = null;
// 自己的后置指针指向自己,帮助GC
f.next = f; // help GC
// 重新设置成员变量为头节点
first = n;
// 如果头节点的后置指针为null 设置后置指针为null
if (n == null)
last = null;
else
// 设置后置指针的前置指针为null
n.prev = null;
--count;
// 唤醒生产者
notFull.signal();
// 返回头节点的item
return item;
}
1.5 PriorityBlockingQueue 优先级队列的原理分析
PriorityBlockingQueue是一个优先级队列,不满足先进先出FIFO的概念,他会将插入的数据进行排序,排序的方式就是插入数据值的本身,排序的方式是基于数组实现的二叉堆。
private transient Object[] queue;
二叉堆的结构介绍
就是一个完整的二叉树,任意一个节点大于父节点或小于父节点(即小顶堆)
PriorityBlockingQueue的写入源码分析
// 默认的初始值为11
private static final int DEFAULT_INITIAL_CAPACITY = 11;
// 队列允许的最大值,-8是为了防止jvm内存溢出
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
// 队列中存放的数据
private transient Object[] queue;
// 队列的长度
private transient int size;
// 比较器
private transient Comparator<? super E> comparator;
// 实现阻塞队列的锁操作
private final ReentrantLock lock = new ReentrantLock();
// 挂起线程操作
private final Condition notEmpty = lock.newCondition();
// 因为PriorityBlockingQueue是基于二叉堆实现的,二叉堆又是基于数组实现的,如果需要扩容,则通过这个属性做标记,控制PriorityBlockingQueue释放lock锁,避免因扩容出现问题
private transient volatile int allocationSpinLock;
//阻塞队列用到的就是普通的优先级队列
private PriorityQueue<E> q;
PriorityBlockingQueue的写入操作
public boolean add(E e){
return offer(e);
}
public boolean offer(E e) {
// 非空判断
if (e == null)
throw new NullPointerException();
//拿到锁
final ReentrantLock lock = this.lock;
//上锁
lock.lock();
int n, cap;
Object[] es;
// 如果元素个数大于等于数组的长度,进行扩容操作
while ((n = size) >= (cap = (es = queue).length))
tryGrow(es, cap);
try {
final Comparator<? super E> cmp;
// 比较数据大小
if ((cmp = comparator) == null)
// 看是否需要做上移操作,并存储数据
siftUpComparable(n, e, es);
else
siftUpUsingComparator(n, e, es, cmp);
size = n + 1;
// 如果有挂起的线程,则去唤醒挂起的消费者
notEmpty.signal();
} finally {
lock.unlock();
}
return true;
}
// 数组扩容操作
private void tryGrow(Object[] array, int oldCap) {
// 扩容时需要释放锁
lock.unlock(); // must release and then re-acquire main lock
Object[] newArray = null;
// 扩容标记位是0代表可以尝试扩容,通过CAS的方式将allocationSpinLock从0修改为1
if (allocationSpinLock == 0 &&
ALLOCATIONSPINLOCK.compareAndSet(this, 0, 1)) {
try {
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) :
(oldCap >> 1));//每次扩容到1.5倍
if (newCap - MAX_ARRAY_SIZE > 0) { // possible overflow
int minCap = oldCap + 1;
// 扩容后的最小数组长度还不满足条件,直接抛出异常
if (minCap < 0 || minCap > MAX_ARRAY_SIZE)
throw new OutOfMemoryError();
// 新长度等于最大长度
newCap = MAX_ARRAY_SIZE;
}
// 新数组长度必须大于老数组长度并且没有并发扩容的出现
if (newCap > oldCap && queue == array)
// 构建新数组
newArray = new Object[newCap];
} finally {
// 标记位归零
allocationSpinLock = 0;
}
}
// 说明这个线程没有构建新数组,设置线程等待一会
if (newArray == null) // back off if another thread is allocating
Thread.yield();
// 拿锁
lock.lock();
// 将数据复制到新数组,执行完整扩容操作
if (newArray != null && queue == array) {
queue = newArray;
System.arraycopy(array, 0, newArray, 0, oldCap);
}
}
// 上移操作,维护二叉堆结构
private static <T> void siftUpComparable(int k, T x, Object[] es) {
// 将插入的元素强转为Comparable,如果对象没有实现Comparable接口,则直接抛出转换异常
Comparable<? super T> key = (Comparable<? super T>) x;
// 如果k==0 说明数组中没有数据,直接将x放在第一个位置即可,否则执行while循环,公式是parent=(son-1)/2
while (k > 0) {
// 拿到父节点的下标
int parent = (k - 1) >>> 1;
// 拿到父节点
Object e = es[parent];
// 进行比较
if (key.compareTo((T) e) >= 0)
break;
es[k] = e;
k = parent;
}
es[k] = key;
}
PriorityBlockingQueue的读取源码分析
读取操作是存在线程挂起的情况的,如果数组元素个数为0,此时线程执行take方法,就需要挂起线程,读取栈顶的数据后,需要重新维护二叉堆结构
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return dequeue();
} finally {
lock.unlock();
}
}
// 取出index=0的数据,并维护二叉堆结构
private E dequeue() {
// assert lock.isHeldByCurrentThread();
final Object[] es;
final E result;
// 将数组复制到局部变量,并取出0位置的数据,强转并赋值给result,若为null 直接返回,否则继续执行
if ((result = (E) ((es = queue)[0])) != null) {
//记录当前数组下标
final int n;
// 取数组中最大下标的数据
final E x = (E) es[(n = --size)];
// 将数组中最大下标设置为null
es[n] = null;
// 下标必须大于0才能继续执行,若等于或小于0说明数组不需要进行下移操作来维护二叉堆结构
if (n > 0) {
final Comparator<? super E> cmp;
if ((cmp = comparator) == null)
siftDownComparable(0, x, es, n);
else
siftDownUsingComparator(0, x, es, n, cmp);
}
}
return result;
}
// 下移操作,维护二叉堆结构
private static <T> void siftDownComparable(int k, T x, Object[] es, int n) {
// assert n > 0;
// 将最大下标的数据进行强转赋值给key,若没有实现Comparable接口,则抛出异常
Comparable<? super T> key = (Comparable<? super T>)x;
// 因为二叉堆是一个二叉满树,所以保证二叉堆结构时,只需要做一半就可以
int half = n >>> 1;
// 超过一半就不需要再继续执行了
while (k < half) {
// 找左侧子节点索引,公式:左移1位+1
int child = (k << 1) + 1; // assume left child is least
// 拿到左子节点数据
Object c = es[child];
// 拿到右子节点的索引
int right = child + 1;
// 确认有右子节点
if (right < n &&
// 判断左节点>右节点
((Comparable<? super T>) c).compareTo((T) es[right]) > 0)
c = es[child = right];
// 比较最后节点是否小于当前较小的子节点
if (key.compareTo((T) c) <= 0)
break;
es[k] = c;
// k 重置到子节点的位置
k = child;
}
es[k] = key;
}