Queue和PriorityQueue,PriorityBlockingQueue
queue
先来了解一下queue队列接口,队列接口继承于Collection接口。队列是一种特殊的线性表,是一种先进先出的数据结构。它只允许在队列头部进行删除操作,在队列尾部进行添加操作,当队列中没有数据时这个队列是空队列。
下面来看一下queue中的方法:
//添加方法
boolean add(E e);
//也是添加方法,如果在添加时队列元素已满,则返回false,不抛出异常
boolean offer(E e)
//移除方法
E remove()
//也是移除方法,主要是为了在移除空的集合对象时不报异常,而是返回null
E poll()
//用于队列的头部查询元素,队列为空时,抛出异常
E element()
//用于队列的头部查询元素,队列为空时,这个方法不抛出异常
E peek()
PriorityQueue
PriorityQueue是优先队列,他可以保证每次从队列取出的元素都是队列中权重最小的元素,下面看下PriorityQueue类中维护的元素:
//默认的队列长度
private static final int DEFAULT_INITIAL_CAPACITY = 11;
//一个不参与序列化的数组,主要是用来存放元素
transient Object[] queue;
//优先队列的长度
private int size = 0;
//比较器,主要是用来排序
private final Comparator<? super E> comparator;
对PriorityQueue的属性有了一些基本的了解,现在来看一下PriorityQueue类中的几个方法,首先是add:
public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)//如果长度和队列的长度相等,就进行扩容
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);//根据下标置入对象
return true;
}
从上面的代码块中可以看到add方法实际上是调用的offer方法。在这里就贴出grow方法:
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
可以看出每次扩容都是根据之前的长度是否小于64,来进行扩容的。队列扩容一次就扩容到MAX_ARRAY_SIZE这么大。
接下来一起来看一下peek方法:
//获取第一位的值
public E peek() {
return (size == 0) ? null : (E) queue[0];
}
由于队列的元素都是存放在数组中的,所以可以直接根据下标来获取。
接下来一起看下remove方法:
public boolean remove(Object o) {
int i = indexOf(o);//先判断是否有这个对象
if (i == -1)
return false;
else {
removeAt(i);
return true;
}
}
private E removeAt(int i) {
// assert i >= 0 && i < size;
modCount++;
int s = --size;//先将长度-1
if (s == i) // removed last element
//说明移除的是最后一位
queue[i] = null;
else {
E moved = (E) queue[s];//mvoed是最后一个对象
queue[s] = null;
siftDown(i, moved);//根据下标找出要移除的对象
if (queue[i] == moved) {
//调整队列,
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}
在removeAt方法中,比较重要的是siftDown和siftUp方法,这两个方法找出啦要移除的对象,并且把对象放到队列中的第一位,下面来看源码:
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
//在这里就只看一个方法,上面两个方法的思路是差不多的
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
if (key.compareTo((E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = key;
}
siftDownComparable方法是通过while循环来把要删除的对象和他左右子节点进行比较,如果比左右子节点小,就把对象和下标值进行交换,直到无法在进行交换,最后把交换后的位置和对象进行赋值。
siftUp方法:
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);//通过比较器置入对象
else
siftUpComparable(k, x);//通过传入对象的比较器置入对象
}
//这里仍然是只贴出一个方法
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)//当前元素和父节点不断比较,如果比父节点小就交换,继续比较,否则就停止
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
siftUp方法是如果对象的位置发生了变化,就把树结构中的数据进行重新排序,跟父节点进行比较,如果比父节点小就进行交换位置。交换完之后判断一下对应下标的对象是否不等于要移除的对象最后返回要移除的对象,否则返回null。
PriorityBlockingQueue
PriorityBlockingQueue类是阻塞优先队列,继承了AbstractQueue类和实现了BlockingQueue接口。下面来看一下PriorityBlockingQueue中维护的几个属性:
//默认的队列长度
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;
看过PriorityBlockingQueue类的属性,来看一下几个构造方法:
//没有传参数时,创建的是默认的队列长度和比较器为null
public PriorityBlockingQueue() {
this(DEFAULT_INITIAL_CAPACITY, null);
//传入的是一个集合时
public PriorityBlockingQueue(Collection<? extends E> c) {
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
boolean heapify = true; // 如果不知道堆顺序,则为真
boolean screen = true; // 如果必须筛选空值,则为true
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的实例
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();
}
private void heapify() {
Object[] array = queue;
int n = size;
int half = (n >>> 1) - 1;//长度除以2-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);//通过比较器
}
}
heapify
在上面的代码块中,传入的参数是集合对象时,会判断这个集合对象是不是SortedSet和PriorityBlockingQueue的实例,如果是的话,则知道堆的排序顺序,不用进行排序。在参数是集合对象时,核心代码时排序,下面来看一下通过比较器来排序的代码:
//使用比较器,以倒序的方式进行排序
private static <T> void siftDownUsingComparator(int k, T x, Object[] array,
int n,
Comparator<? super T> cmp) {
if (n > 0) {//判断队列的长度
int half = n >>> 1;//长度除以2,
while (k < half) {
int child = (k << 1) + 1;//传入的k乘2+1
Object c = array[child];
int right = child + 1;//提前获取到下一个对象的下标
if (right < n && cmp.compare((T) c, (T) array[right]) > 0)//判断right是不是小于队列的长度,然后比较c和right下标的对象
c = array[child = right];//如果c比right下标的对象大,就把C赋值到right位置
if (cmp.compare(x, (T) c) <= 0)//判断x是否比c小,小的话就跳出此次循环
break;
array[k] = c;
k = child;
}
array[k] = x;
}
}
看到这里你会发现在进入siftDownUsingComparator先把size除以2-1,然后进行排序的,这是因为PriorityBlockingQueue是一个堆的二叉树结构,这样可以得到二叉树中的第一个节点。然后就进行循环插入元素。
add
public boolean add(E e) {
return offer(e);
}
//这个方法添加元素当队列已满时不会抛出异常,而是返回false
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;
}
//尝试扩容
private void tryGrow(Object[] array, int oldCap) {
lock.unlock(); // must release and then re-acquire main lock释放锁
Object[] newArray = null;
if (allocationSpinLock == 0 &&
UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
0, 1)) { //CAS比较 compareAndSwapInt等同于compareAndSet
try {
int newCap = oldCap + ((oldCap < 64) ?
(oldCap + 2) : // grow faster if small
(oldCap >> 1));//扩容50%
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;
}
}
//如果另一个线程正在分配,则让出
if (newArray == null) // back off if another thread is allocating
Thread.yield();//放弃低优先级的CPU资源
lock.lock();//获取锁
if (newArray != null && queue == array) {
queue = newArray;
System.arraycopy(array, 0, newArray, 0, oldCap);
}
}
在添加一个元素时,会通过offer方法来进行添加,通过lock来获得锁之后判断队列的长度是否大于等于数组的长度,为真的话就循环进行扩容,记下来看tryGrow方法,即是尝试扩容,可以看到在这里先把锁进行了释放,这里通过CAS无锁化来避免了锁的竞争,try块里面是扩容的逻辑,这里不再细说,在finally中即使扩容完成,就会把allocationSpinLock这个属性设为0。如果当前有线程正在进行扩容时,就放弃低优先级的线程,目的是让扩容线程在扩容完毕之后可以优先获得锁。
siftUpUsingComparator
在添加元素时,尝试扩容的方法已经看完,接下来看使用比较器把元素置入到队列中:
//建堆算法
private static <T> void siftUpUsingComparator(int k, T x, Object[] array,
Comparator<? super T> cmp) {
while (k > 0) {
int parent = (k - 1) >>> 1;//长度减一除以2
Object e = array[parent];
if (cmp.compare(x, (T) e) >= 0)//如果x比e大,则跳出while循环
break;//跳出循环
array[k] = e;
k = parent;
}
array[k] = x;
}
在添加的方法中,主要的核心是在添加元素时对堆结构中元素进行排序。可以看到,通过比较器把元素置入到队列中时,放入的对象会和父节点位置的对象进行比较,如果放入的对象比父节点的对象要小,则进行交换。
poll
上面看完了添加的方法,这里来看一下移除的方法,废话不说,直接贴出代码:
//poll方法是queue接口中的方法,所以使用poll方法来移除空的集合时是不会报异常的,默认移除的是第一位的对象
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return dequeue();
} finally {
lock.unlock();
}
}
//dequeue是移除的核心,从这里可以看出offer默认移除的是第一位的元素
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;
}
}
//使用比较器,以正序的方式进行重排序
private static <T> void siftDownUsingComparator(int k, T x, Object[] array,
int n,
Comparator<? super T> cmp) {
if (n > 0) {//判断队列的长度
int half = n >>> 1;//长度除以2
while (k < half) {
int child = (k << 1) + 1;//传入的k乘2+1
Object c = array[child];
int right = child + 1;//提前获取到下一个对象的下标
if (right < n && cmp.compare((T) c, (T) array[right]) > 0)//判断right是不是小于队列的长度,然后比较c和right下标的对象
c = array[child = right];//如果c比right下标的对象大,就把C赋值到right位置
if (cmp.compare(x, (T) c) <= 0)//判断x是否比c小,小的话就跳出循环
break;
array[k] = c;
k = child;
}
array[k] = x;
}
}
移除方法默认删除存放数据的数组中的第一位元素,并且在移除之后会对数组的数据进行重排序。
remove
再了解完移除方法时,我们知道了移除方法默认移除的是第一位元素,下面来看一下移除指定的元素的代码:
public boolean remove(Object o) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
int i = indexOf(o);//获取要移除元素的下标
if (i == -1)
return false;
removeAt(i);
return true;
} finally {
lock.unlock();
}
}
private void removeAt(int i) {
Object[] array = queue;
int n = size - 1;
if (n == i) // removed last element
array[i] = null;
else {
E moved = (E) array[n];
array[n] = null;
Comparator<? super E> cmp = comparator;
if (cmp == null)
siftDownComparable(i, moved, array, n);
else
siftDownUsingComparator(i, moved, array, n, cmp);
if (array[i] == moved) {
if (cmp == null)
siftUpComparable(i, moved, array);
else
siftUpUsingComparator(i, moved, array, cmp);
}
}
size = n;
}
移除阻塞优先队列中的对象时,会先获取要移除对象的下标,接下来就和移除第一位元素的思路是一样的。