阻塞队列&优先级队列源码解读

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值