JDK容器与并发—Queue—PriorityBlockingQueue

概述

      基于优先堆的无界阻塞队列,PriorityQueue的线程安全版本。

数据结构

      基于数组的平衡二叉堆,在PriorityQueue基础上,增加了一把锁、一个条件:

private transient Object[] queue;

// 增删查公用的锁
private final ReentrantLock lock;

// 队列为空时,阻塞take/poll线程的条件
private final Condition notEmpty;

 // 自旋锁,用CAS方式获取,用于动态扩展queue
private transient volatile int allocationSpinLock;

构造器

      与PriorityQueue几乎一样,除了需要对lock、notEmpty初始化:

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

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

public PriorityBlockingQueue(int initialCapacity,
							 Comparator comparator) {
	if (initialCapacity < 1)
		throw new IllegalArgumentException();
	this.lock = new ReentrantLock();		// lock、notEmpty初始化
	this.notEmpty = lock.newCondition();
	this.comparator = comparator;
	this.queue = new Object[initialCapacity];
}

public PriorityBlockingQueue(Collection 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 ss = (SortedSet) c;
		this.comparator = (Comparator) ss.comparator();
		heapify = false;
	}
	else if (c instanceof PriorityBlockingQueue) {
		PriorityBlockingQueue pq =
			(PriorityBlockingQueue) c;
		this.comparator = (Comparator) 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();
}

增删查

容器调整策略(避免无限制扩展)

      步骤(与PriorityQueue大致相同,除了采用自旋锁的方式动态分配数组,在获取公用锁下复制queue):
1)当queue已满,若有元素入队请求,则进行容量扩展;
2)在获取公用锁lock的前提下,释放lock,采用自旋锁的方式动态扩展数组,允许与take/poll线程并发,完成分配后再重新获取lock;
3)oldCap小于64则容量翻倍;否则增长50%;
4)检查newCap是否在MAX_ARRAY_SIZE范围内,若minCap有overflow或大于MAX_ARRAY_SIZE,抛出OutOfMemoryError异常;否则容量最大不超过MAX_ARRAY_SIZE;
5)动态分配新容量的Object[];
6)获取公用锁,将旧queue中的元素复制过来。

while ((n = size) >= (cap = (array = queue).length)) // while使用是为了采用自旋锁进行扩展queue
		tryGrow(array, cap);

// 动态扩展queue		
// 释放公用锁,采用自旋锁,允许扩展过程中与take/poll线程并发,避免其在该过程中的等待
private void tryGrow(Object[] array, int oldCap) {
	lock.unlock(); 				// 释放公用锁
	Object[] newArray = null;
	if (allocationSpinLock == 0 &&				// 采用CAS方式获取allocationSpinLock,进行动态分配
		UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,
								 0, 1)) {
		try {
			int newCap = oldCap + ((oldCap < 64) ?
								   (oldCap + 2) : // grow faster if small
								   (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 {
			allocationSpinLock = 0;
		}
	}
	if (newArray == null) // 其他线程正进行扩展,当前线程yield
		Thread.yield();
	lock.lock();			    // 重新获取公用锁
	if (newArray != null && queue == array) {      // 复制队列
		queue = newArray;
		System.arraycopy(array, 0, newArray, 0, oldCap);
	}
}

基础方法

      与PriorityQueue一样,除了增加两个参数:Object[] array、Comparator cmp,以保证并发性:

// 对元素x,从k往前移,保持二叉堆的平衡性
private static  void siftUpUsingComparator(int k, T x, Object[] array,
								   Comparator 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;
}

// 对元素x,从k往后移,保持二叉堆的平衡性
private static  void siftDownUsingComparator(int k, T x, Object[] array,
												int n,
												Comparator 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;
	}
}

      步骤(过程与PriorityQueue一样,除了考虑并发性加锁):
1)获取公用锁lock;
2)检查队列是否已满,若满则采用自旋锁的方式进行容量扩展;
3)从队尾,对元素进行siftUp,保持二叉堆的平衡性;
4)向take/poll线程发送notEmpty信号;
5)释放锁lock;
6)返回true。

另外,由于PriorityBlockingQueue无界,add、put操作都直接委托给offer进行:

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 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一样,除了考虑并发性加锁):
1)获取公用锁lock;
2)检查队列是否为空,为空则返回null;
3)取出队列最后一个元素,从索引0开始,对其进行siftDown,保持二叉堆的平衡性;
4)释放锁lock;
5)返回队首元素值。

public E poll() {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
		return dequeue();
	} finally {
		lock.unlock();
	}
}

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

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

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 cmp = comparator;
		if (cmp == null)
			siftDownComparable(0, x, array, n);
		else
			siftDownUsingComparator(0, x, array, n, cmp);
		size = n;
		return result;
	}
}

      步骤(过程与PriorityQueue一样,除了考虑并发性加锁):
1)获取公用锁lock;
2)检查队列是否为空,为空则返回null;
3)释放锁lock;
4)返回队首元素值。

public E peek() {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
		return (size == 0) ? null : (E) queue[0];
	} finally {
		lock.unlock();
	}
}

迭代器

      不保证元素的迭代顺序,基于底层数组的副本实现:

public Iterator iterator() {
	return new Itr(toArray());
}

final class Itr implements Iterator {
	final Object[] array; // Array of all elements
	int cursor;           // index of next element to return
	int lastRet;          // index of last element, or -1 if no such

	Itr(Object[] array) {
		lastRet = -1;
		this.array = array;
	}

	public boolean hasNext() {
		return cursor < array.length;
	}

	public E next() {
		if (cursor >= array.length)
			throw new NoSuchElementException();
		lastRet = cursor;
		return (E)array[cursor++];
	}

	public void remove() {
		if (lastRet < 0)
			throw new IllegalStateException();
		removeEQ(array[lastRet]);
		lastRet = -1;
	}
}

特性

PriorityBlockingQueue中优先级相同的元素处理

      若多个元素的优先级相同,则其顺序是不固定的,可以采用二级比较方法来进一步排序,以下示例为按照元素的入队顺序进行二级比较:

class FIFOEntry>
		implements Comparable> {
	static final AtomicLong seq = new AtomicLong(0);
	final long seqNum;
	final E entry;
	public FIFOEntry(E entry) {
		seqNum = seq.getAndIncrement();
		this.entry = entry;
	}
	public E getEntry() { return entry; }
	public int compareTo(FIFOEntry other) {
		int res = entry.compareTo(other.entry);
		if (res == 0 && other.entry != this.entry)
			res = (seqNum < other.seqNum ? -1 : 1);
		return res;
	}
}

为什么PriorityBlockingQueue的操作不直接在委托给PriorityQueue基础上加锁实现?

      allocationSpinLock在动态扩展queue上的使用使得委托+lock是实现不了的。

PriorityBlockingQueue就是PriorityQueue的加锁线程安全版。

你可能感兴趣的:(JDK容器与并发)