PriorityBlockingQueue源码分析

基于jdk1.8源码进行分析的。

看完了两个BlockingQueue的实现类,下面我们来看一下PriorityBlockingQueue的实现,也就是有优先级的阻塞队列。根据前面的经验大致猜测,感觉下来,知道了对应的源码实现过程,下面去验证一下自己的想法是否正确。

类继承结构

public class PriorityBlockingQueue<E> extends AbstractQueue<E>
    implements BlockingQueue<E>, java.io.Serializable 

继承了AbstractQueue类,实现了BlockingQueue接口和Serializable接口。

成员属性

    //默认的初始化大小
    private static final int DEFAULT_INITIAL_CAPACITY = 11;
    //最大容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    //这个数组代表的是一个平衡二叉堆,即queue[n]的子节点为queue[2n+1]和queue[2*(n+1)]
    private transient Object[] queue;
    //优先队列中的元素个数
    private transient int size;
    //比较器,如果为空,则为自然顺序
    private transient Comparator<? super E> comparator;
    //lock锁
    private final ReentrantLock lock;
    //为空时,进行阻塞的Condition
    private final Condition notEmpty;
    //优先队列:主要用于序列化,这是为了兼容之前的版本。只有在序列化和反序列化才非空
    private PriorityQueue<E> q;

构造方法

PriorityBlockingQueue()

默认构造方法,默认容量大小为11,没有比较器,按照自然顺序排序。

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

通过源码看到,是通过调用另外一个构造方法实现的。

PriorityBlockingQueue(int initialCapacity)

初始化的时候指定容量大小,按照自然顺序进行排序。

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

通过源码看到,是通过调用另外一个构造方法实现的。

public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator)

    public PriorityBlockingQueue(int initialCapacity,
                                 Comparator<? super E> comparator) {
        //数据合法性校验
        if (initialCapacity < 1)
            //不合法,抛出异常
            throw new IllegalArgumentException();
        //初始化的时候初始化锁
        this.lock = new ReentrantLock();
        //初始化condition
        this.notEmpty = lock.newCondition();
        //构造器初始化
        this.comparator = comparator;
        //初始化数组,二叉堆
        this.queue = new Object[initialCapacity];
    }

初始化的时候,可以指定容量大小以及对应的元素比较器。

核心方法

put(E e)

    public void put(E e) {
        offer(e); // never need to block
    }

可以看到是通过调用内部方法offer实现的。

offer(E e)

    public boolean offer(E e) {
        //数据合法性校验
        if (e == null)
            //不合法抛出异常
            throw new NullPointerException();
        //记录lock锁 
        final ReentrantLock lock = this.lock;
        //上锁
        lock.lock();
        //声明一些变量
        int n, cap;
        //声明一个数组
        Object[] array;
        //n=size
        //array=queue
        //cap = queue.length
        //如果此时数组已满,则需要扩容。
        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大小
            size = n + 1;
            //唤醒消费者线程
            notEmpty.signal();
        } finally {
            //释放锁
            lock.unlock();
        }
        //返回结果
        return true;
    }

tryGrow(Object[] array, int oldCap)

二叉堆扩容方法 

private void tryGrow(Object[] array, int oldCap) {
    lock.unlock(); //释放获取的锁
    Object[] newArray = null;

    //cas成功则扩容(4)
    if (allocationSpinLock == 0 &&
        UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
                                 0, 1)) {
        try {
            //oldGap<64则扩容新增oldcap+2,否者扩容50%,并且最大为MAX_ARRAY_SIZE
            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;
        }
    }

    //第一个线程cas成功后,第二个线程会进入这个地方,然后第二个线程让出cpu,尽量让第一个线程执行下面点获取锁,但是这得不到肯定的保证。(5)
    if (newArray == null) // 如果两外一个线程正在分配,则让出
        Thread.yield();
    lock.lock();//(6)
    if (newArray != null && queue == array) {
        queue = newArray;
        System.arraycopy(array, 0, newArray, 0, oldCap);
    }
}

可以看到这里先释放了锁,当然不释放锁也是可以的。这话怎么讲?也就意味着整个扩容阶段和过程都持有对应的锁,那么对于其他线程来讲就只能阻塞了,等待扩容,操作完毕释放锁,那么操作如果比较耗时,也就意味着并发性能比较差。所以为了提高性能,使用CAS控制只有一个线程可以进行扩容,并且在扩容前释放了锁,让其他线程可以进行入队和出队操作。spinlock锁使用CAS控制只有一个线程可以进行扩容,CAS失败的线程会调用Thread.yield() 让出 cpu,目的是为了让扩容线程扩容后优先调用 lock.lock 重新获取锁,但是这得不到一定的保证。有可能yield的线程在扩容线程扩容完成前已经退出,并获取到了锁。如果当前数组扩容还没完毕,当前线程会再次调用tryGrow方法,然后释放锁,这又给扩容线程获取锁提供了机会,如果这时候扩容线程还没扩容完毕,则当前线程释放锁后又调用yield方法让出CPU。可知当扩容线程进行扩容期间,其他线程是原地自旋通过代码检查当前扩容是否完毕,等扩容完毕后才退出代码的循环。当扩容线程扩容完毕后会重置自旋锁变量allocationSpinLock 为 0,这里并没有使用UNSAFE方法的CAS进行设置是因为同时只可能有一个线程获取了该锁,并且 allocationSpinLock 被修饰为了 volatile。当扩容线程扩容完毕后会获取锁,获取锁后复制当前 queue 里面的元素到新数组。

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

没有指定比较器,也就是采用自然顺序进行比较的时候,此时调用的方法,也就是整个二叉堆建堆的过程。

    //k = size , x = e, array = queue
    private static <T> void siftUpComparable(int k, T x, Object[] array) {
        Comparable<? super T> key = (Comparable<? super T>) x;
        //队列元素个数>0则判断插入位置,否者直接入队
        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;
    }

实现思路如下:

首先把要添加的元素加到数组的末尾,然后和它的父节点(位置为当前位置减去1再除以2取整(k-1)/2,比如第4个元素的父节点位置是1,第7个元素的父节点位置是3)比较,如果新元素比父节点元素大则交换这两个元素,然后再和新位置的父节点比较,直到它的父节点不再比它小,或者已经到达顶端,即第1的位置。

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

使用比较器来构建堆。这里可以看到跟前面相比,最大的区别就在于比较器。

    //k = size , x = e, array = queue cmp=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;
    }

下面我们来看一下take方法的源码分析。

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;
    }

dequeue()

出队操作具体实现 

    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;
        }
    }

整个都是关于二叉堆的操作。思路大体如下:删除位置1的元素,把最后一个元素移到最前面,然后和它的两个子节点比较,如果两个子节点中较小的节点小于该节点,就将它们交换,直到两个子节点都比此顶点大。

涉及到的方法源码如下:

    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;           // loop while a non-leaf
            //在数组中,位置k的节点的子节点的下标肯定小于half
            while (k < half) {
                //左子节点的下标
                int child = (k << 1) + 1; // assume left child is least
                Object c = array[child];
                //右子节点的下标
                int right = child + 1;
                //先比较左右子节点谁小,始终保持c为两子节点中最小的。
                if (right < n &&
                    ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
                    c = array[child = right];
                //如果key值大于子节点的值,则向后沉。否则不变
                if (key.compareTo((T) c) <= 0)
                    break;
                array[k] = c;
                k = child;
            }
            array[k] = key;
        }
    }

    //函数功能:将元素x插入到array[k]处,并自己指定的比较器进行相应的调整,使之保持为最小堆
    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;
        }
    }

以上就是整个核心函数的源码分析过程了,如果不熟悉二叉堆的相关操作,可能看起来稍微有些吃力。不过我们可以看一下DEMO演示。

package demo;

import java.util.concurrent.PriorityBlockingQueue;

/**
 * PriorityBlockingQueue测试例程
 * @ClassName:   PriorityBlockingQueueDemo  
 * @Description: PriorityBlockingQueue测试例程
 * @author       BurgessLee
 * @date         2019年5月5日  
 *
 */
public class PriorityBlockingQueueDemo {
	
	public static void main(String[] args) {
		PriorityBlockingQueue<ComparableEntity> priorityBlockingQueue = new PriorityBlockingQueue<ComparableEntity>();
		ComparableEntity c1 = new ComparableEntity(1);
		ComparableEntity c2 = new ComparableEntity(1);
		ComparableEntity c3 = new ComparableEntity(1);
		priorityBlockingQueue.put(c1);
		priorityBlockingQueue.put(c2);
		priorityBlockingQueue.put(c3);
		System.out.println("调用take之前" +priorityBlockingQueue);
		try {
			ComparableEntity res = priorityBlockingQueue.take();
			System.out.println("调用take方法,返回的结果是res=" + res.getId());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("调用take之后" +priorityBlockingQueue);
	}
	
}

class ComparableEntity implements Comparable<ComparableEntity>{

	private Integer id;
	
	public ComparableEntity() {
		super();
	}

	public ComparableEntity(Integer id) {
		super();
		this.id = id;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	@Override
	public int compareTo(ComparableEntity o) {
		return this.id.compareTo(o.id);
	}
	
}

输出结果:

调用take之前[demo.ComparableEntity@4e25154f, demo.ComparableEntity@70dea4e, demo.ComparableEntity@5c647e05]
调用take方法,返回的结果是res=1
调用take之后[demo.ComparableEntity@5c647e05, demo.ComparableEntity@70dea4e]

以上就是所有演示代码测试例程了。如果有不对的地方,还请指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值