[java队列]——PriorityBlockingQueue

PriorityBlockingQueue介绍

上一篇[[java队列]——PriorityQueue介绍了优先级队列,回顾一下PriorityQueue有哪些特点:

  • 数据实现,自动扩容,无界
  • 非线程安全
  • 使用小顶堆作为实现,入队就是堆的插入,出队就是堆的删除堆顶元素

这一篇将介绍另一个优先级队列PriorityBlockingQueue,阻塞式的优先级队列,它相比PriorityQueue有以下特点

  • 线程安全
  • 阻塞
  • 同样是数组实现,自动扩容,无界,同样底层是使用小顶堆实现优先级

PriorityBlockingQueue内部实现

基本属性

public class PriorityBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable {
    //默认初始容量
    private static final int DEFAULT_INITIAL_CAPACITY = 11;
    //数组最大容量
    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;
    //可重入锁上的非空条件
    private final Condition notEmpty;
    //???通过这个变量CAS更新成功的,就可以进行扩容
    private transient volatile int allocationSpinLock;
    //???据说用于序列化
    private PriorityQueue<E> q;

结论:

  • 数组实现
  • 可重入锁+非空条件控制并发
  • 为什么不需要非满条件,因为队列是无界的
  • 结构基本与PriorityQueue一样,只是加多了锁,扩容使用了一个volatile变量CAS来控制并发。

构造方法

	public PriorityBlockingQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }

    public PriorityBlockingQueue(int initialCapacity) {
        this(initialCapacity, null);
    }

    public PriorityBlockingQueue(int initialCapacity,
                                 Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        this.comparator = comparator;
        this.queue = new Object[initialCapacity];
    }

结论:

  • 构造方法非常简单,直接看代码吧

入队

还是跟前面介绍的一样,阻塞队列一般有四个入队方法。基本都是调用了offer(E e)方法

	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[] array;
        //循环判断如果元素个数大于等于数组长度,则进行扩容。
        while ((n = size) >= (cap = (array = queue).length))
        	//调用tryGrow完毕要继续判断,因为调用tryGrow返回并不代表当前线程已经扩容完毕,有可能是其他线程正在扩容,
        	//如果其他线程正在扩容,当前线程的tryGrow将会直接返回,要继续while循环判断,直到其他线程扩容完毕。
            tryGrow(array, cap);
        try {
            Comparator<? super E> cmp = comparator;
            //入队方法与PriorityQueue类似,这里就不再介绍了
            if (cmp == null)
                siftUpComparable(n, e, array);
            else
                siftUpUsingComparator(n, e, array, cmp);
            size = n + 1;
            //入队成功,发出信号
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
        return true;
    }

    public void put(E e) {
        offer(e); // never need to block
    }
    
    public boolean offer(E e, long timeout, TimeUnit unit) {
        return offer(e); // never need to block
    }

结论:

  • 入队加锁,入队完毕,发出非空信号通知
  • 循环判断数组元素个数是否已满,进行tryGrow扩容

扩容

private void tryGrow(Object[] array, int oldCap) {
		//这里先释放锁,目的是由于已经达到扩容条件了,如果有多个线程正在尝试入队,这些线程将会被阻塞
		//为了减少阻塞的线程,这里直接释放锁,使用CAS的方式进行扩容。
        lock.unlock(); // must release and then re-acquire main lock
        Object[] newArray = null;
        //CAS更新allocationSpinLock,更新成功才能进行扩容
        if (allocationSpinLock == 0 &&
            UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                     0, 1)) {
            try {
            	//跟PriorityQueue一样的扩容增量规则
                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;
            }
        }
        //若newArray为null,说明cas更新失败,其他线程正在扩容。这个时候将当前线程让出cpu
        if (newArray == null) // back off if another thread is allocating
        //yield是让当前线程让出cpu,从运行状态变为就绪状态,但并不代表,cpu就一定会先执行其他线程。因为是抢占式的,有可能又抢到cpu
            Thread.yield();
        //扩容成功,或者,线程让出cpu后返回回来,继续加锁
        lock.lock();
        //如果扩容成功并且就数组没有被替换过。。没看太懂这里??
        if (newArray != null && queue == array) {
        	//队列更新为新数组
            queue = newArray;
            //复制新数组
            System.arraycopy(array, 0, newArray, 0, oldCap);
        }
    }

结论:

  • 扩容容量规则与PriorityQueue一致
  • 扩容前先释放锁,为了减少阻塞线程的数量。
  • 扩容前先释放锁,利用CAS来控制扩容。CAS更新失败,则让当前线程让出CPU
  • 最后再次加锁,再确认是否扩容成功

出队

同样出队有四个方法

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        //出队加锁,响应中断
        lock.lockInterruptibly();
        E result;
        try {
        	//若出队队列为空,阻塞等待,dequeue与PriorityQueue的出队一样,这里就不介绍了
            while ( (result = dequeue()) == null)
                notEmpty.await();
        } finally {
        	//解锁
            lock.unlock();
        }
        return result;
    }

	public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        E result;
        try {
            while ( (result = dequeue()) == null && nanos > 0)
                nanos = notEmpty.awaitNanos(nanos);
        } finally {
            lock.unlock();
        }
        return result;
    }

结论:

  • 出队加锁,若dequeue返回为空则一直阻塞,直到收到队列不为空的通知或者中断才会返回
  • dequeue方法基本跟PriorityQueue一致

PriorityBlockingQueue总结

  • 出入队线程安全,使用锁和非空条件实现
  • 扩容线程安全,使用CAS
  • 阻塞
  • 与PriorityQueue同样是数组实现,自动扩容,无界,同样底层是使用小顶堆实现优先级,出入队的堆操作也一样
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章