Java并发编程(十三)PriorityBlockingQueue

188 阅读5分钟

PriorityBlockingQueue是优先级阻塞队列,实现跟PriorityQueue基本一模一样

1 PriorityQueue

非阻塞的优先级队列,底层基于数组实现,可以扩容,是真正意义上的无界队列,优先级基于二叉堆实现

1.1 二叉堆:

  • 二叉堆是一颗完整的二叉树
  • 任意一个节点大于父节点 或者 小于父节点

image.png

1.2 构造器

  • 构造器内可以穿入比较器,通过比较器来决定是大顶堆还是小顶堆
    // 默认小顶堆
    public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }

    // 数组的初始长度
    public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }
    
    // 比较器,决定大顶堆还是小顶堆
    public PriorityQueue(Comparator<? super E> comparator) {
        this(DEFAULT_INITIAL_CAPACITY, comparator);
    }
    
    // 实际的初始化构造器
    public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        // Note: This restriction of at least one is not actually needed,
        // but continues for 1.5 compatibility
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }

  • 也可以通过传入Collection和PriorityQueue来初始化,如果Collection是SortedSet,会继续使用内部的比较器
    public PriorityQueue(Collection<? extends E> c) {
        if (c instanceof SortedSet<?>) {
            SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
            // 使用SortedSet的比较器
            this.comparator = (Comparator<? super E>) ss.comparator();
            initElementsFromCollection(ss);
        }
        else if (c instanceof PriorityQueue<?>) {
            PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;
            // 使用PriorityQueue的比较器
            this.comparator = (Comparator<? super E>) pq.comparator();
            initFromPriorityQueue(pq);
        }
        else {
            // 其他情况默认null
            this.comparator = null;
            initFromCollection(c);
        }
    }


    public PriorityQueue(PriorityQueue<? extends E> c) {
        this.comparator = (Comparator<? super E>) c.comparator();
        initFromPriorityQueue(c);
    }

    public PriorityQueue(SortedSet<? extends E> c) {
        this.comparator = (Comparator<? super E>) c.comparator();
        initElementsFromCollection(c);
    }

1.3 存取数据(保证二叉堆结构)

1.3.1 存数据保证二叉堆结构

// 优先级队列添加操作,确定如何保证小顶堆结构
public boolean offer(E e) {
    if (e == null)
        throw new NullPointerException();
    modCount++;
    // size是数组数据条数,大于等于数组长度后,需要扩容
    int i = size;
    if (i >= queue.length)
        // Double size if small; else grow by 50%
        grow(i + 1);
    // size + i,数据多一条
    size = i + 1;
    // 如果i == 0,说明添加的是第一个数据
    if (i == 0)
        queue[0] = e;
    else
        // 不是第一个数据,Up上移保证结构
        siftUp(i, e);
    return true;
}

// 让当前节点和父节点比较,如果当前节点比较小,就上移
private void siftUpUsingComparator(int k, E x) {
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = queue[parent];
        if (comparator.compare(x, (E) e) >= 0)
            break;
        queue[k] = e;
        k = parent;
    }
    queue[k] = x;
}

1.3.2 取数据保证二叉堆结构

// 取堆顶数据
public E poll() {
    // 没有数据返回null
    if (size == 0)
        return null;
    // 最后一个数据的索引
    int s = --size;
    // 需要全都的数据
    E result = (E) queue[0];
    // 取出最后一个数据
    E x = (E) queue[s];
    // 将最后一个数据置位null
    queue[s] = null;
    if (s != 0)
        // 下移保证安全
        siftDown(0, x);
    return result;
}

// 堆顶数据下移,知道last数据可以存放的位置,然后替换即可
private void siftDownUsingComparator(int k, E x) {
    while (k < half) {
        int child = (k << 1) + 1;
        // 找到左子
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];

        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

2 PriorityBlockingQueue

PriorityBlockingQueue底层基于数组,可以扩容,实现跟PriorityQueue基本一模一样,只是PriorityBlockingQueue基于Lock锁实现了多线程操作安全

2.1 构造器

构造器基本与PriorityQueue相同,仅有添加Collection的方法略有不同

    public PriorityBlockingQueue(Collection<? extends E> c) {
        // 不能排序时为true
        boolean heapify = true; // true if not known to be in heap order
        // null值筛选
        boolean screen = true;  // true if must screen for nulls
        if (c instanceof SortedSet<?>) {
            SortedSet<? extends E> ss = (SortedSet<? extends E>) c;
            this.comparator = (Comparator<? super E>) ss.comparator();
            // 已有比较器
            heapify = false;
        }
        else if (c instanceof PriorityBlockingQueue<?>) {
            PriorityBlockingQueue<? extends E> pq =
                (PriorityBlockingQueue<? extends E>) c;
            this.comparator = (Comparator<? super E>) pq.comparator();
            // 从PriorityBlockingQueue获取数据初始化,不需要考虑null
            screen = false;
            if (pq.getClass() == PriorityBlockingQueue.class) // exact match
                // 已有比较器
                heapify = false;
        }
        Object[] es = c.toArray();
        int n = es.length;
        if (c.getClass() != java.util.ArrayList.class)
            es = Arrays.copyOf(es, n, Object[].class);
        if (screen && (n == 1 || this.comparator != null)) {
            // 筛选null值
            for (Object e : es)
                if (e == null)
                    throw new NullPointerException();
        }
        this.queue = ensureNonEmpty(es);
        this.size = n;
        if (heapify)
            // 排序处理
            heapify();
    }

2.2 PriorityBlockingQueue存取数据源码分析

2.2.1 存数据

存数据不需要挂起线程,所有的存数据最终都是调用offer方法

  • offer方法
// 所有添加都走这里,没有await挂起的方式,
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(array, cap);
    try {
        Comparator<? super E> cmp = comparator;
        if (cmp == null)
            //添加数据到二叉堆
            siftUpComparable(n, e, array);
        else
            siftUpUsingComparator(n, e, array, cmp);
        size = n + 1;
        // 唤醒读线程
        notEmpty.signal();
    } finally {
        lock.unlock();
    }
    return true;
}

// 跟PriorityQueue一样的上移操作
private static <T> void siftUpComparable(int k, T x, Object[] array) {
    Comparable<? super T> key = (Comparable<? super T>) x;
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = array[parent];
        if (key.compareTo((T) e) >= 0)
            break;
        array[k] = e;
        k = parent;
    }
    array[k] = key;
}
  • put和offer(E e, long timeout, TimeUnit unit)都是直接调用offer()
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
}
  • 扩容操作:允许多线程并发扩容的。(不是协助扩容),但是只有一个线程会成功,基于CAS的方式,避免并发问题
private void tryGrow(Object[] array, int oldCap) {
    lock.unlock();

    Object[] newArray = null;
    // 线程将allocationSpinLock从0改为1(CAS),得到了扩容的权利,可以创建新数组
    if (allocationSpinLock == 0 && UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,0, 1)) {
        try {
            // 计算新数组长度
            int newCap = oldCap + ((oldCap < 64) ? (oldCap + 2) :  (oldCap >> 1));
            // 判断长度是否超过界限
            if (newCap - MAX_ARRAY_SIZE > 0) {  
                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) 
        // 如果newArray是null,说明当前线程没有执行扩容操作
        // 让出CPU时间片,尽量让扩容的线程先走完扩容操作
        Thread.yield();
    lock.lock();
    if (newArray != null && queue == array) {
        queue = newArray;
        // 扩容结束
        System.arraycopy(array, 0, newArray, 0, oldCap);
    }
}

2.2.2 取数据

PriorityBlockingQueue的读操作,是允许使用condition挂起的,因为二叉堆可能没有数据。

  • dequeue,实际的取数据并处理堆结构的方法,队列为空时会返回null
private E dequeue() {
    int n = size - 1;
    if (n < 0)
        return null;
    else {
        Object[] array = queue;
        // 拿到堆顶数据
        E result = (E) array[0];
        E x = (E) array[n];
        array[n] = null;
        Comparator<? super E> cmp = comparator;
        if (cmp == null)
            // 保证结构,下移
            siftDownComparable(0, x, array, n);
        else
            siftDownUsingComparator(0, x, array, n, cmp);
        size = n;
        return result;
    }
}
  • poll,取数据,加锁调用dequeue,队列为空返回null
public E poll() {
    // 基于lock锁保证安全,
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return dequeue();
    } finally {
        lock.unlock();
    }
}
  • poll,取数据,队列为空时超时等待,超时后返回null
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;
}
  • take,取数据,队列为空时阻塞
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    E result;
    try {
        // 队列为空时阻塞
        while ( (result = dequeue()) == null)
            notEmpty.await();
    } finally {
        lock.unlock();
    }
    return result;
}