来谈谈 BlockingQueue 阻塞队列实现类 java.util.concurrent.PriorityBlockingQueue(JDK1.8 源码分析)

PriorityBlockingQueue源码刨析



前言

public class PriorityBlockingQueue
extends AbstractQueue
implements BlockingQueue, java.io.Serializable {
在这里插入图片描述


提示:以下是本篇文章正文内容,下面案例可供参考

一、PriorityBlockingQueue源码部分

1.构造方法

public PriorityBlockingQueue() {
    
    
    this(DEFAULT_INITIAL_CAPACITY, null); //DEFAULT_INITIAL_CAPACITY = 11
}

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

//上面两个构造器主要调用了下面的方法

public PriorityBlockingQueue(int initialCapacity,
                             Comparator<? super E> comparator) {
    
     //指定比较器确定优先级
    if (initialCapacity < 1)
        throw new IllegalArgumentException(); //指定的初始化容量小于1抛出非法异常
    this.lock = new ReentrantLock(); //锁初始化
    this.notEmpty = lock.newCondition(); //用于线程的阻塞和唤醒操作的成员变量
    this.comparator = comparator; //初始化比较器
    this.queue = new Object[initialCapacity]; //初始化底层数组
}

//传入一个集合进行初始化
public PriorityBlockingQueue(Collection<? extends E> c) {
    
    
    this.lock = new ReentrantLock(); //锁初始化
    this.notEmpty = lock.newCondition();//用于线程的阻塞和唤醒操作的成员变量
    boolean heapify = true; // true if not known to be in heap order
    boolean screen = true;  // true if must screen for nulls
    if (c instanceof SortedSet<?>) {
    
     //集合对象是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; //传入集合是优先级队列子类则不需null值检查
        if (pq.getClass() == PriorityBlockingQueue.class) // exact match
            heapify = false; //传入集合是优先级队列子类则不需堆的重建
    }
    Object[] a = c.toArray(); //复制c队列底层数组
    int n = a.length; //数组长度
    // If c.toArray incorrectly doesn't return Object[], copy it.
    if (a.getClass() != Object[].class) //如果a数组不是object类型数组将转换成object类型数组
        a = Arrays.copyOf(a, n, Object[].class);
    if (screen && (n == 1 || this.comparator != null)) {
    
    
        for (int i = 0; i < n; ++i) //队列中的元素不能为null
            if (a[i] == null)
                throw new NullPointerException(); //空指针异常
    }
    this.queue = a; //将a赋值给队列的数组
    this.size = n; //队列的长度
    if (heapify)
        heapify(); //传入的集合不是PriorityBlockingQueue的字类(或无序)将堆重建
}


//以下对toArray()底层进行研究
public Object[] toArray() {
    
     //toArray是Cellection接口的一个方法(每个集合类实现该方法)
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    
    
        return Arrays.copyOf(queue, size);
    } finally {
    
    
        lock.unlock();
    }
}

//Arrays中方法进行研究(数组拷贝的方法)
public static <T> T[] copyOf(T[] original, int newLength) {
    
    
// original.getClass()该参数传入的必须为数组的class
    return (T[]) copyOf(original, newLength, original.getClass());
}

//original:原数组
// newLength:新数组的长度
// newType:新数组的class类型
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    
    
    @SuppressWarnings("unchecked")
/*这里先对数组类型进行判断,如果是object类型的数组直接new,其他类型的数组则通过反射的方式创建数组,做这样的判断是因为直接new 的方式进行创建数组效率要比反射的方式创建数组要高很多*/
/*对于(Object)newType == (Object)Object[].class)为什么要进行强转,是因为Class<? extends T[]>和Object[].class是无法比较的类型(编译会报错),强转以后才可以通过编译*/
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
//这里进行数组的拷贝(调用了本地方法)
/*arraycopy方法,从数组original起始位置0开始复制到数组copy的起始位置从0开始,长度为Math.min(original.length, newLength),这里注意如果newLength超过了原来数组的长度将直接去原来数组的长度。*/
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
    return copy;
}

2.成员变量

private final ReentrantLock lock; //应用于所有的public操作

private final Condition notEmpty; // Condition for blocking when empty

private transient Comparator<? super E> comparator; //比较器

/**
 * Priority queue represented as a balanced binary heap: the two
 * children of queue[n] are queue[2*n+1] and queue[2*(n+1)].  The
 * priority queue is ordered by comparator, or by the elements'
 * natural ordering, if comparator is null: For each node n in the
 * heap and each descendant d of n, n <= d.  The element with the
 * lowest value is in queue[0], assuming the queue is nonempty.
 */
//该队列是一个堆,数组位置n的元素的左右子节点分别为2 * n + 1 和 2 * n + 2
//指定了比较器,队列根据比较器排序
//没有指定比较器,进行自然排序,前面的元素小于后面的元素
private transient Object[] queue; 

private static final int DEFAULT_INITIAL_CAPACITY = 11; //默认数组大小

3.主要方法

heapify建堆方法

//堆重建
private void heapify() {
    
    
    Object[] array = queue; //获取优先级队列的数组
    int n = size; //数组的长度
    int half = (n >>> 1) - 1; // ( n / 2 – 1 )
    Comparator<? super E> cmp = comparator; //获取优先级队列的比较器
    if (cmp == null) {
    
     //比较器为null
        for (int i = half; i >= 0; i--) //得到一个小顶堆(从中间开始)
            siftDownComparable(i, (E) array[i], array, n);
    }
    else {
    
     //比较器不为null
        for (int i = half; i >= 0; i--)
            siftDownUsingComparator(i, (E) array[i], array, n, cmp);
    }
//通过以上的下沉操作实际得到了一个堆
}

//没有指定比较器的情况
private static <T> void siftDownComparable(int k, T x, Object[] array,
                                           int n) {
    
    
    if (n > 0) {
    
     //数组的是大于0的
        Comparable<? super T> key = (Comparable<? super T>)x; //获取要比较元素比较器
        int half = n >>> 1;
/*下面的循环实际上是(完全二叉树的)一个下沉操作,k是我们要下沉元素的数组下标,x(或key)为要下沉的元素,循环终止条件实际上是当前节点已经没有子节点了或者当前节点是小于或等于左右子节点的。我们先获取当前节点的左右子节点,然后进行比较如果左右子节点有比当前节点小的,则把当前节点的位置赋值给最小的那个节点,然后设置当前位置k为前面被移到父节的节点的位置,循环操作直到当前节点比左右子节点都小或等于左右子节点或当前节点不存在左右子节点退出循环*/
        while (k < half) {
    
    
             //左节点下标(这里的child的范围是不可能超出n的,因为 k < half)
            int child = (k << 1) + 1;
            Object c = array[child]; //c为左节点的元素
            int right = child + 1; //右节点下标
            if (right < n && //右节点范围在n内
                //左右子节点进行比较
                ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
                c = array[child = right]; //左子节点大于右子节点,取小的
            if (key.compareTo((T) c) <= 0) //如果父节点已经是最小的元素了退出循环
                break;
            array[k] = c; //将三个元素中最小得元素放在位置k上
            k = child; //k等于刚刚最小节点的下标,进行下一轮的下沉
        }
        array[k] = key;
    }
}

//指定了比较器的情况(与上面方法类似唯一不同的地方在于指定了比较器)
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;
    }
}

有上面队列的初始化过程可知优先级队列实际上是用的数组来维护的一个堆,该堆的父节点都是大于子节点的,下面出队和入队操作都需对该堆的维护。(对堆不同熟悉的可以看我process on 上堆排序部分:https://www.processon.com/view/5e973347e401fd262e1c207f

入队方法

//队列满时该方法不会阻塞,而是会进行扩容操作
public void put(E e) {
    
    
    offer(e); // never need to block
}

//这个方法,指定的2 ,3参数无效
public boolean offer(E e, long timeout, TimeUnit unit) {
    
    
    return offer(e); // never need to block
}

public boolean offer(E e) {
    
    
    if (e == null)
        throw new NullPointerException(); //队列中元素不能为null
    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; //入队成功size加一
        notEmpty.signal(); //唤醒那些被阻塞在出队操作的线程
    } finally {
    
    
        lock.unlock(); //释放锁
    }
    return true; //入队成功返回true
}


//队列的扩容方法tryGrow
private void tryGrow(Object[] array, int oldCap) {
    
    
    lock.unlock(); //释放所持有的全局锁(此时其他线程是可以进行入队或出队操作的)
    Object[] newArray = null; //扩容的新数组
    if (allocationSpinLock == 0 &&//由于之前释放了锁这里才有cas保证只有一个线程在进行扩容操作
        UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                 0, 1)) {
    
    
        //具体扩容过程
        try {
    
    
            //新的数组大小更具旧的数组大小来设定
            int newCap = oldCap + ((oldCap < 64) ?
                                   (oldCap + 2) : //之前数组越小扩容的越慢
                                   (oldCap >> 1)); // 2被速扩容
            if (newCap - MAX_ARRAY_SIZE > 0) {
    
     //新数组比被限制的最大容量还大 
                int minCap = oldCap + 1; 
            //旧容量只增加一个仍然超出最大容量
            if (minCap < 0 || minCap > MAX_ARRAY_SIZE) 
                    throw new OutOfMemoryError(); //内存溢出错误
                newCap = MAX_ARRAY_SIZE; //指定为最大容量
            }
            // queue != array 时表示其他线程已经扩容成功的情况
            if (newCap > oldCap && queue == array)
                newArray = new Object[newCap];//创建新数组
        } finally {
    
    
            allocationSpinLock = 0; //设置为0其他想进行扩容的线程可以扩容了
        }
    }
    //由于其他线程已经在进行扩容操作导致当前线程扩容失败(或者容量已经到达最大限制)
    if (newArray == null) 
        Thread.yield(); //进行线程让步,暂时让出CPU资源
    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[] array) {
    
    
    Comparable<? super T> key = (Comparable<? super T>) x; //获取x的比较器
    while (k > 0) {
    
     
        int parent = (k - 1) >>> 1; //父节点(k – 1)/ 2
        Object e = array[parent]; //父节点元素
        if (key.compareTo((T) e) >= 0)
            break; //父节点小于当前节点跳出循环
        array[k] = e; //否则父节点移到当前节点的位置上来
        k = parent; //k变为父节点的位置
    }
    //上面循环是一个上升的过程,直到上升到根节点或者当前节点大于父节点为止
    array[k] = key;
}

//指定了比较器的情况(与上面方法类似只是使用了指定的比较器)
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;
}

由上面的分析我们可以看到入队操作实际上都是调用的offer方法,在队列满了的时候不会阻塞而是直接进行扩容操作,而扩容操作时会释放掉全局锁,此时其他线程是可以进行出队入队操作的,而扩容操作通过CAS操控allocationSpinLock成员变量保证了当前只有一个线程在进行扩容。

出队

//如果队列空了,一直阻塞,直到队列不为空或者线程被中断
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; //返回结果
}

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; //队尾元素设为null
        Comparator<? super E> cmp = comparator; //获取比较器
//一下将队尾元素作为新元素插入到队首,然后进行下沉操作
        if (cmp == null) //无比较器的情况
            siftDownComparable(0, x, array, n); //将队尾元素放在第一个位置进行下沉操作
        else //比较器不为null的情况
            siftDownUsingComparator(0, x, array, n, cmp);
        size = n; //size-1
        return result; //返回出队后的结果
    }
}

//poll方法出队操作队列为null时直接返回null不会阻塞
public E poll() {
    
    
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    
    
        return dequeue();
    } finally {
    
    
        lock.unlock();
    }
}

//该方法与上面方法类似队列为null时直接返回null
//不为null时直接返回队首元素,但不会移除队首的元素
public E peek() {
    
    
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    
    
        return (size == 0) ? null : (E) queue[0];
    } 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;
}


总结

  1. PriorityBlockingQueue底层使用了一个小顶堆的数据结构(即父节点总是小于左右子节点的一个完全二叉树),因此每次进行入队操作时先将元素放在队列尾部,让后通过上升操作来维护堆的结构,而每次进行出队操作都会返回队首元素然后将队尾元素放在第一个对其进行下沉操作。
  2. PriorityBlockingQueue 是一个无界队列入队操作是不会阻塞的(入队元素不能为null否则抛异常),由于器底层使用的是数组长度是固定的,因此当容量不足时会进行扩容操作,扩容操作通过CAS保证器安全性,并且当容量小于 64时都是以加2的方式进行扩容,大于等于64时则以2被方式扩容。出队是队列为空时将会放入一个等待队列中等待其他线程将其唤醒。
  3. PriorityBlockingQueue 是一个优先级队列 ,其优先级是根据指定比较器或元素的实现的比较器来进行比较的,并且比较小的总是放在第一个(优先级最高)
  4. 全局使用了一把锁,同时只能进行读写种的一个。

猜你喜欢

转载自blog.csdn.net/weixin_41237676/article/details/109103909
今日推荐