PriorityBlockingQueue 概览

PriorityBlockingQueue 是带 优 先级的无界 阻塞队列,每次出队都返回优先级最高或者 最低的元素。其内部是使用平衡二叉树堆实现的,所以直接遍历队列元素不保证有序。默 认使用对象的 compareTo 方法提供比较规则,如果你需要自定义比较规则则可以自定义 comparators 。

PriorityBlockingQueue 内 部有一个数组 queue,用来存放队列元素, size用来存放队列元素个数。 allocationspinLock是个自旋锁,其使用 CAS操作来保证同 时只有一个线程可以扩容队列,状态为 0或者 1,其中 0表示当前没有进行扩容, 1表示 当前正在扩容。

由于这是一个优先级队列,所以有一个比较器 comparator用来比较元素大小。 lock独 占锁对象用来控制同时只能有一个线程 可以进行 入队、出队操作。 notEmpty 条件变量用 来实现 take 方法阻塞模式。这里没有 notFull 条件变量是因为这里的 put 操作是非阻塞的,

为啥要设计为非阻塞的,是因为这是无界队列。

默认队列容量为 11,默认比较器为 null,也就是使用元素的 compareTo方法进行比较来确定元素的优先级, 这意味着队列元素必须实现了 Comparable 接口。 扩容 64 个元素之内, 在原来的容量上+2, 大于 64 , 每次扩容 0.5 倍.

 

 

类图:

offer操作

offer操作的作用是在队列中插入一个元素,由于是无界队列 , 下是 offer函数的代码。

  public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        //todo 获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        int n, cap;
        Object[] array;

        //如果当前元素个数〉=队列容量,则扩容
        while ((n = size) >= (cap = (array = queue).length))
            //todo 扩容操作
            tryGrow(array, cap);
        try {
            Comparator<? super E> cmp = comparator;
            if (cmp == null)
                //todo 如果自定定义比较器走这里
                siftUpComparable(n, e, array);
            else
                //todo 使用类自带的 compareTo 方法
                siftUpUsingComparator(n, e, array, cmp);

            //todo 将队列元素数增加 1, 并且激活 notEmpty的条件队列里面的 一 个阻塞线程
            size = n + 1;
            notEmpty.signal();
        } finally {
            //释放独占锁
            lock.unlock();
        }
        return true;
    }

 

扩容

初始容量 11.  元素个数小于 64 ,每次扩容 2. 大于 64 则每次扩容原数组的 0.5 倍.

为了提高性能, 使用 CAS控制只有一个 线程可 以进行扩容,并且在扩容前释放锁,让其他线程可以进行入队和出队操作 。

spinlock锁使用 CAS控制只有一个线程可以进行扩容, CAS失败的线程会调用 Thread.yield() 让出 CPU, 目的是让扩容线程扩容后 优 先调 用 lock.lock 重新获取锁,但是 这得不到保证。有可能 yield 的线程在扩容线程扩容完成前己经退 出,

扩容线程扩容完毕后会重置自旋锁变量 allocationSpinLock 为 0,这里并没有使 用 UNSAFE 方法的 CAS 进行设置是因为 同时 只可 能 有一个线程获取到该锁 , 并且 allocationSpinLock 被修饰 为 了 volatile 的。

private void tryGrow(Object[] array, int oldCap) {
        //todo 释放获取的锁
        lock.unlock(); // must release and then re-acquire main lock

        Object[] newArray = null;
        if (allocationSpinLock == 0 &&
                UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                        0, 1)) {


            try {

                //todo oldGap<64则扩容 ,执行oldcap+2,否则扩容50%,并且最大为 MAX一皿RAY_SIZE

                int newCap = oldCap + ((oldCap < 64) ?
                        (oldCap + 2) : // grow faster if small
                        (oldCap >> 1));
                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;
            }


        }

        //todo 第一个线程CAS成功后,第 二个线程会进入这段代码 ,
        // 然后第二个线程让出 CPU,尽量让第一个线程 获取锁,但是这得不到保证。
        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);
        }
    }

PriorityBlockingQueue 队列 在内部使用二叉树堆维护元素优先级,使用数组作为元素 存储的数据结构,这个数组是可扩容的 。当当 前元素个数>=最大容量时会通过 CAS 算法 扩容,出队时始终保证出队的元素是堆树的根节点,而不是在队列里面停留时 间最长的元 素。使用元素的 compareTo 方法提供默认的元素优先级比较规则,用户可以自定义优先级 的比较规则。

PriorityBlockingQueue类似于 ArrayBlockingQueue,在内部使用一个独占锁来 控制同时只有一个线程可以进行入队和出队操作。另外,前者只使用了一个 notEmpty 条件变量而没有使用 notFull,这是因为前者是无界队列,执行 put操作时永远不 会处于 await 状态,所以也不需要被唤醒。而 take 方法是阻塞方法,并且是可被中断的 。 当需要存放有优先级的元素时该队列比较有用 。

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值