JDK8系列:阻塞队列 之 PriorityBlockingQueue(优先级队列)源码分析

1、PriorityBlockingQueue 简介

(1)PriorityBlockingQueue整个入队出队的过程与PriorityQueue基本是保持一致的;

(2)PriorityBlockingQueue使用一个锁+一个notEmpty条件控制并发安全;

(3)PriorityBlockingQueue扩容时使用一个单独变量的CAS操作来控制只有一个线程进行扩容;

(4)入队使用自下而上的堆化;

(5)出队使用自上而下的堆化;

该阻塞队列每次取出的都是最小的对象(小顶堆!!!),可以满足一定的实际场景

阻塞队列PriorityBlockingQueue从不阻塞写线程,而当队列元素为空时,会阻塞读线程的读取,当然也有非阻塞的方法(poll)。该阻塞队列适用于读多于写的场景,不然,写线程过多,会导致内存消耗过大,影响性能。

(1)写线程不阻塞,读线程在队列为空时阻塞

当队列为满时,写线程不会阻塞,而会尝试去扩容,扩容成功就继续向阻塞队列写入数据。当队列为空时,读线程会阻塞等待,直到队列不为空,被写线程唤醒。因此该阻塞队列适用于读多于写的场景,不然,写线程过多,会导致内存消耗过大,影响性能读写线程共用同一把独占锁

阻塞队列

(2)堆存储结构

阻塞队列PriorityBlockingQueue每次取出的都是最小(或最大)的对象,而其底层的存储结构正是堆存储(ps:堆结构需要满足每一个父节点小于(或大于)每一个子节点),所以每次向阻塞队列添加的时候,需要根据元素大小,调整其在堆中的位置,注意不需要对所有元素排序,只对父子节点排序,只要满足堆性质即可。这样每次从阻塞队列中取出元素时,直接取出堆顶元素,即最小值(或最大值),然后重新调整堆

(3)扩容机制

当阻塞队列PriorityBlockingQueue满时,写线程会尝试扩容(扩增old+2或者old/2),注意,写线程扩容的时候会先释放独占锁,并获取一个扩容独占锁(此锁仅用于扩容,防止多个写线程同时扩容),这样做的目的是提高读线程的吞吐量。扩容的本质其实就是新建一个更大的object数组,然后把阻塞队列PriorityBlockingQueue中的原阻塞队列引用替换成新的数组。

(4)PriorityBlockingQueue需要元素自然排序或者提供比较器

前面说过,阻塞队列PriorityBlockingQueue使用的是堆存储结构,那么就需要对父子节点进行排序,所以需要阻塞队列PriorityBlockingQueue存储的元素实现了Comparable接口,或者在新建阻塞队列PriorityBlockingQueue的时候提供比较器。

(1)依然是使用一个数组来使用元素;

(2)使用一个锁加一个notEmpty条件来保证并发安全;

(3)使用一个变量的CAS操作来控制扩容;

 

2、成员变量 和 构造方法

public class PriorityBlockingQueue<E> extends AbstractQueue<E> 
                                                implements BlockingQueue<E>, java.io.Serializable {
    // 序列号
    private static final long serialVersionUID = 5595510919245408276L;

    // 默认数组容量
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    // 能分配的最大数组容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    // 构成queue的数组
    private transient Object[] queue;

    // queue 中当前的元素数量
    private transient int size;

    // 比较器,如果优先级队列使用元素自然排序,则为空
    private transient Comparator<? super E> comparator;

    // 独占锁,读写线程共用这一把锁
    private final ReentrantLock lock;

    // 读线程等待队列,写线程永远不会阻塞
    private final Condition notEmpty;

    // 写线程扩容锁,通过CAS控制,只有一个写线程会将此变量从0变成1
    private transient volatile int allocationSpinLock;

    // 不阻塞的优先级队列,非存储元素的地方,仅用于序列化/反序列化时
    private PriorityQueue<E> q;


    private static final sun.misc.Unsafe UNSAFE;
    private static final long allocationSpinLockOffset;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = PriorityBlockingQueue.class;
            allocationSpinLockOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("allocationSpinLock"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

/*********************  下面是构造方法   ***********************/
    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];
    }

    // 将参数集合c中的元素放入queue
    public PriorityBlockingQueue(Collection<? extends E> c) {
        this.lock = new ReentrantLock();
        this.notEmpty = lock.newCondition();
        boolean heapify = true; // 不知道堆的order时,尾true
        boolean screen = true;  // 如果必须筛选空值,则为true
        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();
            screen = false;
            if (pq.getClass() == PriorityBlockingQueue.class) // exact match
                heapify = false;
        }
        Object[] a = c.toArray();
        int n = a.length;
        // If c.toArray incorrectly doesn't return Object[], copy it.
        if (a.getClass() != Object[].class)
            a = Arrays.copyOf(a, n, Object[].class);   //复制数据
        if (screen && (n == 1 || this.comparator != null)) {
            for (int i = 0; i < n; ++i)
                if (a[i] == null)
                    throw new NullPointerException();
        }
        this.queue = a;
        this.size = n;
        if (heapify)
            heapify();
    }
}

 

3、方法介绍

扩容tryGrow() ,将元素入队时,如果容量满了,就扩容。越小扩容越快

增加元素的4个方法:add(E e)、offer(E e) 、offer(E e, long timeout, TimeUnit unit)、put(E e)   都不阻塞或等待。因为如果容量满,就扩容。

删除元素的方法里面,take() 还是个阻塞方法。

 不管是 添加元素 还是 删除元素,都要加锁,而且 是同一把锁 

siftUpComparable、siftUpUsingComparator、siftDownComparable、siftDownUsingComparator 方法体现了堆排序的思想

3.1、扩容 | 建堆

tryGrow(Object[] array, int oldCap)

// 写线程扩容
private void tryGrow(Object[] array, int oldCap) {
    // 先释放锁,因为是从offer()方法的锁内部过来的	
    // 这里先释放锁,使用allocationSpinLock变量控制扩容的过程	
    // 防止阻塞的线程过多	
    lock.unlock(); 
    Object[] newArray = null;  

    // CAS更新allocationSpinLock变量为1的线程获得扩容资格
    if (allocationSpinLock == 0 && 
                UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,0, 1)) {
        try {
            // 确定新的容量;小时增长的快
            // 当oldCap<64时,newCap = oldCap + (oldCap+2)
            // 当oldCap>=64时,newCap = oldCap + oldCap/2;
            int newCap = oldCap + ((oldCap < 64) ?
                                (oldCap + 2) : (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 {
            // 相当于解锁,这里可以不用CAS,因为只有一个线程能获取allocationSpinLock成功
            allocationSpinLock = 0;
        }
    }

    // 如果有其他线程正在扩容,则当前线程进入睡眠
    if (newArray == null) 
        Thread.yield();

    // 再次加锁
    lock.lock();

    // 判断新数组创建成功并且旧数组没有被替换过
    if (newArray != null && queue == array) {
        queue = newArray;  // 队列赋值为新数组
        System.arraycopy(array, 0, newArray, 0, oldCap);  //并拷贝旧数组元素到新数组中
    }
}

heapify()  

// 对数组queue进行键堆
// 就是从下向上(half-->0)作为起点,依次向下遍历调整位置
// 就再构造方法里面用过
private void heapify() {
    Object[] array = queue;
    int n = size;
    int half = (n >>> 1) - 1;
    Comparator<? super E> cmp = comparator;
    if (cmp == null) {
        for (int i = half; i >= 0; i--)
            siftDownComparable(i, (E) array[i], array, n);
    }
    else {
        for (int i = half; i >= 0; i--)
            siftDownUsingComparator(i, (E) array[i], array, n, cmp);
    }
}

 

3.2、增加数据

add(E e)

public boolean add(E e) {
    return offer(e);
}

offer(E e) 

// 将数据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(array, cap);
    try {
        Comparator<? super E> cmp = comparator;
        // 插入元素是插到堆底,原本是有序的
        // 根据是否有比较器选择不同的方法,将e放到堆中合适的地方(从n堆底开始向上遍历)
        if (cmp == null)
            siftUpComparable(n, e, array);
        else
            siftUpUsingComparator(n, e, array, cmp);
        size = n + 1;   // queue中元素个数+1
        notEmpty.signal();   //释放非空信号,唤醒take|poll等线程
    } finally {
        lock.unlock();   // 释放锁
    }
    return true;
}

offer(E e, long timeout, TimeUnit unit)      从不阻塞

// 不会进行超时等待,因为入队不需要等待,容量不够则扩容!!!
public boolean offer(E e, long timeout, TimeUnit unit) {
    return offer(e); // 从不阻塞
}

put(E e)      从不阻塞

public void put(E e) {
    offer(e); // 从不阻塞,理由同上
}

 

3.3、删除数据

dequeue()

// 出队
private E dequeue() {
    int n = size - 1;   //计算出队后,queue的元素个数
    // 如果当前queue为空,则返回null
    if (n < 0)
        return null;
    else {
        Object[] array = queue;  
        E result = (E) array[0];  // 取出队首元素
        // 将队尾元素取出来赋给x,并将原来的队尾置空(!!!)
        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()
public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();    //加锁
    try { 
        return dequeue();   //调用出队函数
    } finally {
        lock.unlock();   //释放锁
    }
}

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();   //加锁,响应中断
    E result;
    try {
        // 如果queue为空,取出来的元素为空,则等待
        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 {
        // 如果queue为空,则进入等待队列
        while ( (result = dequeue()) == null)
            notEmpty.await();
    } finally {
        lock.unlock();   //释放锁
    }
    return result;
}

removeAt(int i) 

注意,这是个private方法。  只被 remove(Object o)  和 removeEQ(Object o) 调用。

// 删除queue中的i处的元素
// 注意:没有加锁。   因为在调用本方法之前,已经加锁了
private void removeAt(int i) {
    Object[] array = queue;
    int n = size - 1;  
    // 情况1:要删除的元素是最后一个元素,则直接置空即可
    if (n == i)
        array[i] = null;
    // 情况2:要删除的元素不是最后一个元素,处理较复杂
    else {
        // 将最后一个元素赋值给moved,并将array[n]置空
        E moved = (E) array[n];  
        array[n] = null;

        // 根据比较器的情况来调用不同的方法,将moved放到i-->n之间的合适位置
        Comparator<? super E> cmp = comparator;
        if (cmp == null)
            siftDownComparable(i, moved, array, n);
        else
            siftDownUsingComparator(i, moved, array, n, cmp);

        // 如果moved没有放到i-->n之间,那么moved可能会放到0-->i之间
        if (array[i] == moved) {
            if (cmp == null)
                siftUpComparable(i, moved, array);
            else
                siftUpUsingComparator(i, moved, array, cmp);
        }
    }
    size = n;   //更新size
}

remove(Object o) 

删除的是 和o.equals()相同的

// 删除元素o
注意:和removeAt(int)不同的是,本方法加锁了,因为是public类型,可自由调用
public boolean remove(Object o) {
    final ReentrantLock lock = this.lock;
    lock.lock();   //加锁
    try {
        int i = indexOf(o);  //找到o的位置
        if (i == -1)
            return false;
        removeAt(i);  //调用removeAt(int)方法删除,注意,此时还是持有锁的
        return true;
    } finally {
        lock.unlock();   //释放锁
    }
}

removeEQ(Object o) 

删除的是 0==array[i]相同的,如果queue存的是对象,那么删除的是同一个对象(内存地址相同)

void removeEQ(Object o) {
    final ReentrantLock lock = this.lock;
    lock.lock();   //加锁
    try {
        Object[] array = queue;
        for (int i = 0, n = size; i < n; i++) {
            if (o == array[i]) {
                removeAt(i);
                break;
            }
        }
    } finally {
        lock.unlock();
    }
}

 

3.4、遍历排序

siftUpComparable(int k, T x, Object[] array)

直接和 parent 比较

// 向上遍历排序,并插入新元素x
// k表示开始向上遍历位置的索引
// x表示插入元素的值,也就是本来是插到k处,但是经过排序之后旧可能不在k处的新元素的值
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;   //插入新元素
}

siftUpUsingComparator(int k, T x, Object[] array, Comparator<? super T> cmp) 

// 向上遍历排序,并插入新元素x
// k是开始线上遍历位置的索引
// x是新元素的值
// cmp 是自定义的比较器
private static <T> void siftUpUsingComparator(int k, T x, Object[] array, Comparator<? super T> cmp) {
    while (k > 0) {
        // 取父节点的值
        int parent = (k - 1) >>> 1;
        Object e = array[parent];

        // 如果 新元素大于父节点,堆化结束
        if (cmp.compare(x, (T) e) >= 0)
            break;
        // 否则,交换二者的位置,继续下一轮比较
        array[k] = e;
        k = parent;
    }
    array[k] = x;  //插入新元素
}

siftDownComparable(int k, T x, Object[] array, int n) 

就是先找到 左右孩子中的较小者,再和 parent 比较

// 向下遍历排序,调整位置
// x是新堆顶的值(也就是原来堆中的最后一个值)   
// k是开始向下遍历位置的索引,一般是0    
// n是x再原来堆中的位置,一般是最后
private static <T> void siftDownComparable(int k, T x, Object[] array, int n) {
    if (n > 0) {
        Comparable<? super T> key = (Comparable<? super T>)x;
        int half = n >>> 1;        // n减半,注意:堆中,父比子小 
        // 只需要遍历到叶子节点就够了
        while (k < half) {   // 如果k==half说明是叶子节点;不可能出现k>half
            // 左子节点
            int child = (k << 1) + 1; 
            Object c = array[child];
            // 右子节点
            int right = child + 1;

            // 取左右子节点中最小的值
            if (right < n &&
                    ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
                c = array[child = right];

            // key如果比左右子节点都小,则堆化结束
            if (key.compareTo((T) c) <= 0)
                break;
            // 否则,交换key与左右子节点中最小的节点的位置
            array[k] = c;
            k = child;
        }
        // 找到了放元素的位置,放置元素
        array[k] = key;
    }
}

siftDownUsingComparator(int k, T x, Object[] array, int n, Comparator<? super T> cmp) 

// 向下遍历排序
// x是新堆顶的值(也就是原来堆中的最后一个值)   
// k是开始向下遍历位置的索引,一般是0    
// n是x再原来堆中的位置,一般是最后
// cmp是自带的比较器
private static <T> void siftDownUsingComparator(int k, T x, Object[] array, int n, Comparator<? super T> cmp) {
    if (n > 0) {
        int half = n >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = array[child];
            int right = child + 1;
            if (right < n && cmp.compare((T) c, (T) array[right]) > 0)
                c = array[child = right];
            if (cmp.compare(x, (T) c) <= 0)
                break;
            array[k] = c;
            k = child;
        }
        array[k] = x;
    }
}

 

3.5、访问元素

peek()

// 访问队头元素
public E peek() {
    final ReentrantLock lock = this.lock;
    lock.lock();   //加锁
    try {
        return (size == 0) ? null : (E) queue[0];
    } finally {
        lock.unlock();
    }
}

 

3.6、查询

size()

// 返回queue中的元素个数
public int size() {
    final ReentrantLock lock = this.lock;
    lock.lock();   //加锁
    try {
        return size;
    } finally {
        lock.unlock();
    }
}

remainingCapacity() 

// 返回queue剩余容量。一位内可以扩容,故为Integer.MAX_VALUE
public int remainingCapacity() {
    return Integer.MAX_VALUE;
}

indexOf(Object o) 

// 返回object再queue中的索引位置,不用加锁
private int indexOf(Object o) {
    if (o != null) {
        Object[] array = queue;
        int n = size;
        // 遍历queue数组查找
        for (int i = 0; i < n; i++)
            if (o.equals(array[i]))
                return i;
    }
    return -1;  //不存在则返回-1
}

contains(Object o)  

// 判断queue是否包含o
public boolean contains(Object o) {
    final ReentrantLock lock = this.lock;
    lock.lock();  //加锁
    try {
        return indexOf(o) != -1;  //起始就是调用indexOf()方法
    } finally {
        lock.unlock();
    }
}

 

3.7、将queue中数据输出到其他数据结构

toArray() 

// 将queue中数据复制到数组中
public Object[] toArray() {
    final ReentrantLock lock = this.lock;
    lock.lock();   //加锁
    try {
        return Arrays.copyOf(queue, size);  //复制
    } finally {
        lock.unlock();
    }
}

toArray(T[] a) 

// 将queue中数据复制到特定类型的数组中
public <T> T[] toArray(T[] a) {
    final ReentrantLock lock = this.lock;
    lock.lock();   //加锁
    try {
        int n = size;
        // 通过反射创建特定类型的数组
        if (a.length < n)
            return (T[]) Arrays.copyOf(queue, size, a.getClass());
        System.arraycopy(queue, 0, a, 0, n);
        if (a.length > n)
            a[n] = null;
        return a;
    } finally {
        lock.unlock();
    }
}

drainTo(Collection<? super E> c) 

// 将queue数据 迁移到集合c中,默认最大值
public int drainTo(Collection<? super E> c) {
    return drainTo(c, Integer.MAX_VALUE);
}

drainTo(Collection<? super E> c, int maxElements) 

public int drainTo(Collection<? super E> c, int maxElements) {
    if (c == null)
        throw new NullPointerException();
    if (c == this)
        throw new IllegalArgumentException();
    if (maxElements <= 0)
        return 0;
    final ReentrantLock lock = this.lock;
    lock.lock();    //加锁
    try {
        int n = Math.min(size, maxElements);
        // 遍历,迁移n个数据到集合c
        for (int i = 0; i < n; i++) {
            c.add((E) queue[0]); // In this order, in case add() throws.
            dequeue();  //出队
        }
        return n;
    } finally {
        lock.unlock();
    }
}

 

3.8、其他方法

iterator()

// 返回迭代器
public Iterator<E> iterator() {
    return new Itr(toArray());
}

 spliterator()

// 返回 可分割迭代器
public Spliterator<E> spliterator() {
    return new PBQSpliterator<E>(this, null, 0, -1);
}

 comparator()

// 返回比较器
public Comparator<? super E> comparator() {
    return comparator;
}

toString()  

// 打印输出
public String toString() {
    final ReentrantLock lock = this.lock;
    lock.lock();   //加锁
    try {
        int n = size;
        if (n == 0)
            return "[]";
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        // 遍历数组
        for (int i = 0; i < n; ++i) {
            Object e = queue[i];
            sb.append(e == this ? "(this Collection)" : e);
            if (i != n - 1)
                sb.append(',').append(' ');
        }
        return sb.append(']').toString();
    } finally {
        lock.unlock();
    }
}

 clear() 

// 清空
public void clear() {
    final ReentrantLock lock = this.lock;
    lock.lock();    //加锁
    try {
        Object[] array = queue;
        int n = size;
        size = 0;
        for (int i = 0; i < n; i++)
            array[i] = null;
    } finally {
        lock.unlock();
    }
}

writeObject(java.io.ObjectOutputStream s) 

// 将queue数据输出到Stream(就是序列化)
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
    lock.lock();  //加锁
    try {
        // avoid zero capacity argument
        q = new PriorityQueue<E>(Math.max(size, 1), comparator);
        q.addAll(this);
        s.defaultWriteObject();
    } finally {
        q = null;
        lock.unlock();
    }
}

readObject(java.io.ObjectInputStream s) 

// 用于反序列化
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
    try {
        s.defaultReadObject();
        int sz = q.size();
        SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, sz);
        this.queue = new Object[sz];
        comparator = q.comparator();
        addAll(q);
    } finally {
        q = null;
    }
}

 

4、内部类

4.1、Itr

4.2、PBQSpliterator

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值