在练习下载模块时需要对下载任务设置优先级,了解到使用PriorityQueue来实现这个功能。
我们先用一下它:
import java.util.Comparator; import java.util.concurrent.PriorityBlockingQueue; public class Main { public static void main(String[] args) { JobPriorityComparator comparator = new JobPriorityComparator(); PriorityBlockingQueue<Job> queue = new PriorityBlockingQueue<Job>(10, comparator); queue.put(new Job(5)); queue.put(new Job(9)); for (Job job : queue) { System.out.println(job); } queue.put(new Job(7)); System.out.println("After put 7----------------"); for (Job job : queue) { System.out.println(job); } try { Job job=queue.take(); System.out.println("The element take from the priorityqueue is:-------------------"); System.out.println(job); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("After take queue head element---------------"); for (Job job : queue) { System.out.println(job); } } } class Job { int priority; public Job(int priority) { this.priority = priority; } @Override public String toString() { return "Job [priority=" + priority + "]"; } } class JobPriorityComparator implements Comparator<Job> { @Override public int compare(Job job1, Job job2) { if (job1.priority < job2.priority) { return 1; } else if (job1.priority > job2.priority) { return -1; } else { return 0; } } }输出结果是:
Job [priority=9] Job [priority=5] After put 7---------------- Job [priority=9] Job [priority=5] Job [priority=7] The element take from the priorityqueue is:------------------- Job [priority=9] After take queue head element--------------- Job [priority=7] Job [priority=5]从这个输出结果,我们初步分析到优先队列保证了队列中按照某种结果来排序的最大值或者最小值总是在队列的头部,而除了头部之外的元素则并没有排序(如在put了优先级为7的元素之后队列中次序是9,5,7,而不是9,7,5)。所以我们需要分析下put和take操作内部的源码来验证我们的假设。
put方法:
/** * Inserts the specified element into this priority queue. As the queue is * unbounded this method will never block. * * @param e the element to add * @throws ClassCastException if the specified element cannot be compared * with elements currently in the priority queue according to the * priority queue's ordering * @throws NullPointerException if the specified element is null */ public void put(E e) { offer(e); // never need to block }offer方法:
/** * Inserts the specified element into this priority queue. * * @param e the element to add * @return <tt>true</tt> (as specified by {@link Queue#offer}) * @throws ClassCastException if the specified element cannot be compared * with elements currently in the priority queue according to the * priority queue's ordering * @throws NullPointerException if the specified element is null */ public boolean offer(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { boolean ok = q.offer(e); assert ok; notEmpty.signal(); return true; } finally { lock.unlock(); } }其中使用了ReentrantLock锁。
offer方法:
/** * Inserts the specified element into this priority queue. * * @return {@code true} (as specified by {@link Queue#offer}) * @throws ClassCastException if the specified element cannot be * compared with elements currently in this priority queue * according to the priority queue's ordering * @throws NullPointerException if the specified element is null */ 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; }如果队列开始为空,则直接设置数组0位。这个操作并没有进行排序。如果数组元素多于一个,则进入siftUp函数。
/** * Inserts item x at position k, maintaining heap invariant by * promoting x up the tree until it is greater than or equal to * its parent, or is the root. * * To simplify and speed up coercions and comparisons. the * Comparable and Comparator versions are separated into different * methods that are otherwise identical. (Similarly for siftDown.) * * @param k the position to fill * @param x the item to insert */ private void siftUp(int k, E x) { if (comparator != null) siftUpUsingComparator(k, x); else siftUpComparable(k, x); }把元素x放在第k号位置上,判断是否设置了比较器。我们是指定了我们的比较接口,故进入siftUpUsingComparator:
private void siftUpUsingComparator(int k, E x) { while (k > 0) { int parent = (k - 1) >>> 1; Object e = queue[parent]; if (comparator.compare(x, (E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = x; }这里使用了类似 堆排序的思想,查找父节点,比较,交换,直到堆顶的元素为最大或者最小。这个的时间复杂度是O(log n),比类似冒泡O(n)。看来Google同学还是有两把刷子!另外使用 移位运算提高效率,而且使用了 >>>无符号右移。
到这里为止,put方法的代码跟踪结束,关键就在于上面这个类堆排序的算法。
下面我们再来看看它的take方法:
public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { try { while (q.size() == 0) notEmpty.await(); } catch (InterruptedException ie) { notEmpty.signal(); // propagate to non-interrupted thread throw ie; } E x = q.poll(); assert x != null; return x; } finally { lock.unlock(); } }如果没有元素,则线程进入等待状态,可能会抛出中断异常(sleep,wait,join状态下被interrupt)。接下来进入poll方法:
public E poll() { if (size == 0) return null; int s = --size; modCount++; E result = (E) queue[0]; E x = (E) queue[s]; queue[s] = null; if (s != 0) siftDown(0, x); return result; }返回队首元素。及时设置队尾元素为null防止内在泄漏。如果剩余元素多于1个,则进入排序模块:
/** * Inserts item x at position k, maintaining heap invariant by * demoting x down the tree repeatedly until it is less than or * equal to its children or is a leaf. * * @param k the position to fill * @param x the item to insert */ private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }
private void siftDownUsingComparator(int k, E x) { int half = size >>> 1; while (k < half) { int child = (k << 1) + 1; Object c = queue[child]; int right = child + 1; if (right < size && comparator.compare((E) c, (E) queue[right]) > 0) c = queue[child = right]; if (comparator.compare(x, (E) c) <= 0) break; queue[k] = c; k = child; } queue[k] = x; }使用堆排序的思想进行数组的重新排序。