【Java並發編程】18、PriorityBlockingQueue源碼分析,

來源:互聯網
上載者:User

【Java並發編程】18、PriorityBlockingQueue源碼分析,

PriorityBlockingQueue是一個基於數組實現的安全執行緒的無界隊列,原理和內部結構跟PriorityQueue基本一樣,只是多了個安全執行緒。javadoc裡面提到一句,1:理論上是無界的,所以添加元素可能導致outofmemoryerror;2.不容許添加null;3.添加的元素使用構造時候傳入Comparator排序,要不然就使用元素的自然排序。

PriorityBlockingQueue是基於優先順序,不是FIFO,這是個好東西,可以用來實現優先順序的線程池,高優先順序的先執行,低優先順序的後執行。跟之前看過的幾個隊列一樣,都是繼承AbstractQueue實現BlockingQueue介面。

對於優先順序的實現,是採用數組來實現堆的,大概樣子畫個圖容易理解:

堆頂元素時最小的,對於各左右子堆也保證堆頂元素最小。

內部結構和構造:

//基於數組實現的,如果構造沒有傳入容量,就是用預設大小private static final int DEFAULT_INITIAL_CAPACITY = 11;/** * 數組最大容量 */private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/** * 優先順序隊列數組,記住queue[n]的2個左右子項目在數組的位置為在queue[2*n+1]和queue[2*(n+1)] */private transient Object[] queue;/** * 隊列元素個數 */private transient int size;/** * 比較子,構造時可以選擇傳入,沒有就null,到時候就使用元素的自然排序 */private transient Comparator<? super E> comparator;/** * 重入鎖控制多有操作 */private final ReentrantLock lock;/** * 隊列為空白的時候條件隊列 */private final Condition notEmpty;/** * 自旋鎖 */private transient volatile int allocationSpinLock;/** * 序列化的時候使用PriorityQueue,這個PriorityBlockingQueue幾乎一模一樣 */private PriorityQueue q;/** * 預設構造,使用預設容量,沒有比較子 */public PriorityBlockingQueue() {    this(DEFAULT_INITIAL_CAPACITY, null);}public PriorityBlockingQueue(int initialCapacity) {    this(initialCapacity, null);}/** * 最終調用的構造 */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];}

內部結構和構造沒有什麼特別的地方,基於數組實現優先順序的堆,記住數組元素queue[n]的左節點queue[2*n+1]和右節點queue[2*(n+1)],每次出隊的都是queue[0]。

看下常用方法:

add、put、offer都是最終調用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<? super E> cmp = comparator; //這個看構造的時候入參,沒有就用自然排序        if (cmp == null)            siftUpComparable(n, e, array); //所有插入都用從底向上調整        else            siftUpUsingComparator(n, e, array, cmp);        size = n + 1;        notEmpty.signal(); //添加後通知非空條件隊列可以take    } finally {        lock.unlock();    }    return true;}//數組擴容private void tryGrow(Object[] array, int oldCap) {    lock.unlock(); // 數組擴容的時候使用自旋鎖,不需要鎖主鎖,先釋放    Object[] newArray = null;    if (allocationSpinLock == 0 &&        UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,                                 0, 1)) { //cas佔用自旋鎖        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) // 到這裡如果是本線程擴容newArray肯定是不為null,為null就是其他線程在處理擴容,那就讓給別的線程處理        Thread.yield();    lock.lock(); //這裡重新重入鎖,因為擴容後還有其他動作    if (newArray != null && queue == array) { //這裡不為null那就複製數組        queue = newArray;        System.arraycopy(array, 0, newArray, 0, oldCap);    }}//所有插入都用從下向上調整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) //如果比父節點大,那就無所謂退出,直接放在k位置            break;        array[k] = e; //比父節點小,按照k位置給父節點,然後從父節點開始繼續向上尋找        k = parent;    }    array[k] = key;}//所有插入都用從底向上調整,跟siftUpComparable方法類似就是比較的時候使用了構造傳入的comparatorprivate static <T> void siftUpUsingComparator(int k, T x, Object[] array,                                   Comparator<? super T> 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;}

所有的添加元素最後都是調用offer方法,2步:擴容+儲存,大體流程為:

1.加鎖,檢查元素數量是否大於等於數組長度,如果是,那就擴容,擴容沒必要使用主鎖,先釋放鎖,使用cas自旋鎖,容量最少翻倍,釋放自旋鎖,可能存在競爭,檢查下,是否擴容,如果擴容那就複製數組,再度加主鎖;

2.看構造入參是否有comparator,有就使用,沒有就自然排序,從數組待插入位置父節點開始比較大,如果大於父節點,那就直接待插入位置插入,否則就跟父節點交換,然後迴圈向上尋找,數量加1,通知非空條件隊列take,最後釋放鎖。

看下幾個出隊操作:

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(); //如果take,數組沒有元素是要阻塞的    } 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;}public E peek() {    final ReentrantLock lock = this.lock;    lock.lock();    try {        return (size == 0) ? null : (E) queue[0]; //只是擷取元素,不移除    } finally {        lock.unlock();    }}//擷取的基本都調用這個方法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 siftDownComparable(int k, T x, Object[] array,                                           int n) {    if (n > 0) { //元素數量大於0,數組非空        Comparable<? super T> key = (Comparable<? super T>)x;        int half = n >>> 1;           // 最後一個葉子節點的父節點位置        while (k < half) {            int child = (k << 1) + 1; // 待調整位置左節點位置            Object c = array[child]; //左節點            int right = child + 1; //右節點            if (right < n &&                ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)                c = array[child = right]; //左右節點比較,取小的            if (key.compareTo((T) c) <= 0) //如果待調整key最小,那就退出,直接賦值                break;            array[k] = c; //如果key不是最小,那就取左右節點小的那個放到調整位置,然後小的那個節點位置開始再繼續調整            k = child;        }        array[k] = key;    }}

出隊的大體流程:

1.加鎖,擷取queue[0],清掉堆的最後一個葉子節點,並將其作為比較節點。等價於把最後一個葉子節點移到了queue[0]位置。然後從頂向下比較,找到新的queue[0]應該在的位置

2.調用從頂向下調整的方法:待調整位置節點左右節點和之前的葉子節點比較,如果之前葉子節點最小,那就直接放入待調整位置,如果是葉子節點小,那就取小的那個放入待調整位置,並且將小的部分重新迴圈尋找,迴圈次數根據2分尋找,基本是元素數量的一半就到找到位置。

再看一個remove,因為remove方法,2中調整方式都用到了:

public boolean remove(Object o) {    final ReentrantLock lock = this.lock;    lock.lock();    try {        int i = indexOf(o); //尋找o在數組中位置        if (i == -1)            return false;        removeAt(i); //remove掉        return true;    } finally {        lock.unlock();    }}//o在數組中的位置private int indexOf(Object o) {    if (o != null) {        Object[] array = queue;        int n = size;        for (int i = 0; i < n; i++)            if (o.equals(array[i]))                return i;    }    return -1;}//remove掉數組指定位置的元素//跟之前take的dequeue相似的地方,dequeue是remove掉0的位置,然後調整也是從0的位置開始調整,這裡是從指定位置調整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]; //跟dequeue一樣也是最後一個葉子節點作為比較        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;}

remove的時候有2個調整,先自頂向下調整,保證最小,然後再向上調整。

出處:http://blog.csdn.net/xiaoxufox/article/details/51860543

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.