java并发编程-PriorityBlockingQueue
来源:互联网 发布:英雄联盟匹配算法 编辑:程序博客网 时间:2024/06/06 20:38
PriorityBlockingQueue要点
- PriorityBlockingQueue类实现了BlockingQueue接口。
- PriorityBlockingQueue是一个无界并发队列。它使用与java.util.PriorityQueue类相同的排序规则。
- 也就是说,进入该队列的元素会自动排序,在获取该队列的元素时该队列的元素是排好序的。
- 不能在此队列中插入null。
- 虽然此队列在逻辑上是无界的,但由于资源耗尽(导致OutOfMemoryError),尝试添加可能会失败。
- 依靠自然排序的优先级队列也不允许插入不可比较的对象(这样做会导致ClassCastException)。
- 插入到PriorityBlockingQueue中的所有元素都必须实现java.lang.Comparable接口。因此,根据您在可比较实施中决定的任何优先级,这些元素将自动排序。
- 请注意,PriorityBlockingQueue不对具有相同优先级的元素(compare()== 0)强制执行任何特定行为。
- 该类及其迭代器实现了Collection和Iterator接口的所有可选方法。
- 方法iterator()中提供的迭代器不能保证以任何特定的顺序遍历PriorityBlockingQueue的元素。如果需要有序遍历,请考虑使用Arrays.sort(pq.toArray())。
- 此外,方法drainTo可以用于按优先级顺序删除一些或所有元素,并将它们放在另一个集合中。
- 这个类的操作不会保证同等优先级的元素的顺序。若要实现同等优先级元素的顺序,需要自己实现类进行比较:
- 例如你要实现按fifo对元素排序的机制:
官方给出的代码如下:
class FIFOEntry<E extends Comparable<? super E>> implements Comparable<FIFOEntry<E>> { 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<E> other) { int res = entry.compareTo(other.entry); if (res == 0 && other.entry != this.entry) res = (seqNum < other.seqNum ? -1 : 1); return res; } }
返回值
请注意PriorityBlockingQueue的返回值情况。
* 由于PriorityBlockingQueue队列可以自动扩容,所以插入元素时,不用等待阻塞。
* 获取元素时,若队列为空,需要阻塞等待。
PriorityBlockingQueue实现原理分析
- PriorityBlockingQueue使用基于数组的二叉堆,和一个公共的锁来实现。
变量定义
// 数组的默认容量大小private static final int DEFAULT_INITIAL_CAPACITY = 11;// 数组的最大容量,源码注释:有的虚拟机可能会使用数组的头部一个字节。private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;// 数组,节点queue[n]的儿子节点是 queue[2*n+1]和queue[2*(n+1)]。// 注:最小堆的定义和使用,详细见《算法导论》private transient Object[] queue;// 队列中元素的个数private transient int size;// 比较器,用来排序private transient Comparator<? super E> comparator;// 对操作进行同步的锁private final ReentrantLock lock;// 基于锁的条件变量private final Condition notEmpty;// 自旋锁,在分配新的空间时进行同步private transient volatile int allocationSpinLock;// 优先队列变量,用来向前兼容private PriorityQueue q;
构造函数
- public PriorityBlockingQueue(int initialCapacity, Comparator
public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator) { if (initialCapacity < 1) throw new IllegalArgumentException(); this.lock = new ReentrantLock(); this.notEmpty = lock.newCondition(); this.comparator = comparator; this.queue = new Object[initialCapacity]; }
- public PriorityBlockingQueue(Collection
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 // 若参数集合c是SortedSet类型,不需要堆化 if (c instanceof 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[]数组中,然后对该数组进行堆化 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();}
内部函数
- siftUpComparable
- 该函数在最小堆的位置k中插入一个元素x。
- 该函数用来调整最小堆中元素的位置,保证插入元素后,满足最小堆的特性。
private static <T> void siftUpComparable(int k, T x, Object[] array) { Comparable<? super T> key = (Comparable<? super T>) x; 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;}
- private static void siftDownComparable(int k, T x, Object[] array, int n)
参数说明:- k: 需要插入元素的位置
- x:需要插入的元素值
- array: 保存堆的数组
- n:目前数组中的元素个数
功能: - 把x插入到堆中。该函数会不断调整堆的结构,让所有的孩子节点的值小于父节点的值。
- 具体的堆调整的步骤详见《算法导论》的堆排序。
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 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]; // 若插入的值小于c,k应该作为父节点,否则k应该作为儿子节点 if (key.compareTo((T) c) <= 0) break; // c是父节点 array[k] = c; k = child; } // 插入的值作为父节点 array[k] = key; }}
- private E 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; } }
- private void tryGrow(Object[] array, int oldCap)
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)) { 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) // back off if another thread is allocating Thread.yield(); lock.lock(); // 复制原来的数组的值到新的数组中 if (newArray != null && queue == array) { queue = newArray; System.arraycopy(array, 0, newArray, 0, oldCap); } }
对外函数
- public E take() throws InterruptedException
- 该函数获取优先队列头部的元素值,并返回
- 若队列为空,则阻塞等待
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 void put(E e)
该函数在向队列中插入元素时,不会阻塞或等待。因为队列的长度可以无限增加。
public void put(E e) { offer(e); // never need to block }
- public boolean offer(E e)
- 该函数向队列中添加一个值为e的元素。
- 若数组已满,调用tryGrow函数动态增加容量。
- 注意:主要扩容的代价是很大的,因为要进行数组的全部复制。
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); // 把元素e插入,并进行堆化 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;}
- public boolean add(E e)
添加一个元素到优先队列中,成功返回true。失败返回false。
public boolean add(E e) { return offer(e); }
- public E poll()
从队列头部获取一个元素,并删除该元素。
public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return dequeue(); } finally { lock.unlock(); } }
PriorityBlockingQueue实战
生产者消费者模型
使用PriorityBlockingQueue编写的生产者消费者模型。
注意,生产者不会阻塞等待。
package BlockingQueue;import java.util.Random;import java.util.concurrent.BlockingQueue;import java.util.concurrent.PriorityBlockingQueue;class PutTask implements Runnable { private BlockingQueue queue; private Random r = new Random(1000); PutTask(BlockingQueue q) { queue = q; } public void run() { while (true) { try { Thread.sleep(2); String value = Integer.toString(r.nextInt(1000)); queue.put(value); System.out.println(Thread.currentThread().getName() + " put value: " + value); } catch (InterruptedException e) { e.printStackTrace(); } } }}class DealTask implements Runnable { private BlockingQueue queue; private Random r = new Random(10000); DealTask(BlockingQueue q) { queue = q; } public void run() { while (true) { try { Thread.sleep(3); String o = (String) queue.take(); System.out.println(Thread.currentThread().getName() + " get value: " + o); } catch (InterruptedException e) { e.printStackTrace(); } } }}public class DoPriorityBQ { public static void main(String[] args) { final BlockingQueue q = new PriorityBlockingQueue(1000); new Thread(new PutTask(q), "producer3").start(); new Thread(new DealTask(q), "consumer2").start(); new Thread(new DealTask(q), "consumer1").start(); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } }}
测试排序功能
以下程序测试优先阻塞队列的排序功能。
package BlockingQueue;import java.util.Random;import java.util.concurrent.BlockingQueue;import java.util.concurrent.PriorityBlockingQueue;public class DoPriorityBQ2 { public static void main(String[] args) throws InterruptedException { final BlockingQueue<Integer> queue = new PriorityBlockingQueue<Integer>(10); Random r = new Random(100); for (int i = 0; i < 10; i++) { int vi = r.nextInt(100); System.out.print(" " + vi); queue.put(vi); } System.out.println("\n"); Integer v; while ((v = queue.take()) != null) { System.out.print(" " + String.valueOf(v)); } }}
程序解读:
* 该程序会先在队列中插入一些随机数,然后再每次取队列的头节点元素,并输出。
* 可以看到,输出的结果是排好序的。
* 该程序没有使用多线程,主要是为了测试程序的排序功能。
PriorityBlockingQueue的总结
- 该队列会自动对插入的元素进行排序,也就是说:在读取队列元素时,该队列已经排好序了。
- PriorityBlockingQueue是使用数组实现的二叉堆,使用对排序算法实现。
- 由于队列会自动扩容,所以队列的写入元素方法不会等待,若应用中需要很高的并发,建议不要使用该队列,而是使用ArrayBlockingQueue。
- 由于队列的扩容操作代价很大 :会申请一个新的队列,并把元素复制到新队列中,所以在实际场景中,应该预先规划好队列的荣容量大小。
- 该队列可能会对每个插入的元素进行堆的调整,所以时间复杂度是O(1)~O(lgn)。
阅读全文
0 0
- java并发编程-PriorityBlockingQueue
- Java并发编程-31-阻塞式优先级列表-PriorityBlockingQueue
- java编程思想笔记-并发之DelayQueue和PriorityBlockingQueue
- JAVA并发编程随笔【一】PriorityBlockingQueue优先级队列
- java 并发工具包 BlockingQueue-PriorityBlockingQueue
- Java并发-类库新组件 - PriorityBlockingQueue 理解
- Java多线程--并发中集合的使用PriorityBlockingQueue
- Java PriorityBlockingQueue 分析
- 【死磕Java并发】-----J.U.C之阻塞队列:PriorityBlockingQueue
- PriorityBlockingQueue
- PriorityBlockingQueue
- PriorityBlockingQueue
- PriorityBlockingQueue
- Java源码阅读之PriorityBlockingQueue
- Java 多线程 优先级队列 PriorityBlockingQueue
- Java并发编程 并发容器
- JAVA并发编程--并发模式
- JAVA并发-并发编程概述
- 利用命令行执行java时,无法加载主类执行方法
- List,Set,Map是否继承自Collection接口? 答:List,Set是,Map不是。 Collection是最基本的集合接口,一个Collection代表一组Object,即Colle
- 初识linux 中的fork()
- SSH框架入门(2)——struts2(2)
- 对开根号判断素数的优化
- java并发编程-PriorityBlockingQueue
- CentOS7.0以上版本linux防火墙问题
- springboot整合mybatis,动态数据源配置
- eclipse和jdk历史版本下载地址
- 第十章 会话管理(六) sessionFactory
- ionic运行问题
- CentOS下JDK 1.8的安装
- ArrayList、Vector、LinkedList的区别及其优缺点?
- C语言——关机小程序