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

返回值

method Throws Exception Special Value Blocks Times Out Insert put(o),offer(o),add(o) Remove poll(),remove(o) take() poll(t) Examine peek()

请注意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)。
原创粉丝点击