1. 程式人生 > >LinkedBlockingQueue原始碼解析(3)

LinkedBlockingQueue原始碼解析(3)

此文已由作者趙計剛授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。


4.3、public E take() throws InterruptedException

原理:

  • 將隊頭元素出隊,如果佇列空了,一直阻塞,直到佇列不為空或者執行緒被中斷

使用方法:

        try {
            abq.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

原始碼:

    /**
     * 出隊:
     * 如果佇列空了,一直阻塞,直到佇列不為空或者執行緒被中斷
     */
    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;//獲取佇列中的元素總量
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();//獲取出隊鎖
        try {
            while (count.get() == 0) {//如果沒有元素,一直阻塞
                /*
                 * 加入等待佇列, 一直等待條件notEmpty(即被其他執行緒喚醒)
                 * (喚醒其實就是,有執行緒將一個元素入隊了,然後呼叫notEmpty.signal()喚醒其他等待這個條件的執行緒,同時佇列也不空了)
                 */
                notEmpty.await();
            }
            x = dequeue();//出隊
            c = count.getAndDecrement();//元素數量-1
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

 

總結:

1、具體入隊與出隊的原理圖

圖中每一個節點前半部分表示封裝的資料x,後邊的表示指向的下一個引用。

1.1、初始化

2018121815154254ea789e-0a48-4dac-a97e-d84e7e278394.jpg


 初始化之後,初始化一個數據為null,且head和last節點都是這個節點。

1.2、入隊兩個元素過後

20181218151557d8e517a5-66a6-4895-a946-cc6e44bf6f72.jpg


這個可以根據入隊方法enqueue(E x)來看,原始碼再貼一遍:

    /**
     * 建立一個節點,並加入連結串列尾部
     * 
     * @param x
     */
    private void enqueue(E x) {
        /*
         * 封裝新節點,並賦給當前的最後一個節點的下一個節點,然後在將這個節點設為最後一個節點
         */
        last = last.next = new Node<E>(x);
    }

其實這我們就可以發現其實真正意義上出隊的頭節點是Head節點的下一個節點。(這也就是Node這個內部類中對next的註釋,我沒有翻譯)

1.3、出隊一個元素後

20181218151610ae8090d1-5303-4540-92f8-67304b4bea5b.jpg


表面上看,只是將頭節點的next指標指向了要刪除的x1.next,事實上這樣我覺的就完全可以,但是jdk實際上是將原來的head節點刪除了,而上邊看到的這個head節點,正是剛剛出隊的x1節點,只是其值被置空了。

這一塊對應著原始碼來看:dequeue()

    /**
     * 從佇列頭部移除一個節點
     */
    private E dequeue() {
        Node<E> h = head;// 獲取頭節點:x==null
        Node<E> first = h.next;// 將頭節點的下一個節點賦值給first
        h.next = h; // 將當前將要出隊的節點置null(為了使其做head節點做準備)
        head = first;// 將當前將要出隊的節點作為了頭節點
        E x = first.item;// 獲取出隊節點的值
        first.item = null;// 將出隊節點的值置空
        return x;
    }

 

2、三種入隊對比:

  • offer(E e):如果佇列沒滿,立即返回true; 如果佇列滿了,立即返回false-->不阻塞

  • put(E e):如果佇列滿了,一直阻塞,直到佇列不滿了或者執行緒被中斷-->阻塞

  • offer(E e, long timeout, TimeUnit unit):在隊尾插入一個元素,,如果佇列已滿,則進入等待,直到出現以下三種情況:-->阻塞

    • 被喚醒

    • 等待時間超時

    • 當前執行緒被中斷

 

3、三種出隊對比:

  • poll():如果沒有元素,直接返回null;如果有元素,出隊

  • take():如果佇列空了,一直阻塞,直到佇列不為空或者執行緒被中斷-->阻塞

  • poll(long timeout, TimeUnit unit):如果佇列不空,出隊;如果佇列已空且已經超時,返回null;如果佇列已空且時間未超時,則進入等待,直到出現以下三種情況:

    • 被喚醒

    • 等待時間超時

    • 當前執行緒被中斷

 

4、ArrayBlockingQueue與LinkedBlockingQueue對比

  • ArrayBlockingQueue:

    • 一個物件陣列+一把鎖+兩個條件

    • 入隊與出隊都用同一把鎖

    • 在只有入隊高併發或出隊高併發的情況下,因為運算元組,且不需要擴容,效能很高

    • 採用了陣列,必須指定大小,即容量有限

  • LinkedBlockingQueue:

    • 一個單向連結串列+兩把鎖+兩個條件

    • 兩把鎖,一把用於入隊,一把用於出隊,有效的避免了入隊與出隊時使用一把鎖帶來的競爭。

    • 在入隊與出隊都高併發的情況下,效能比ArrayBlockingQueue高很多

    • 採用了連結串列,最大容量為整數最大值,可看做容量無限

 兩個疑問:

  • 入隊時:c==0是怎樣出現的?

  • 出隊時:c==capcity是怎樣出現的?

這兩個疑問,都是基於對於AtomicInteger的不熟,不明白LinkedBlockingQueue引用的這兩個方法(getAndIncrement和getAndDecrement)先返回舊值還是新值,關於AtomicInteger的原始碼介紹,請檢視《第十一章 AtomicInteger原始碼解析》,具體連結如下:

http://www.cnblogs.com/java-zhao/p/5140158.html


免費領取驗證碼、內容安全、簡訊傳送、直播點播體驗包及雲伺服器等套餐

更多網易技術、產品、運營經驗分享請點選


相關文章:
【推薦】 網易雲terraform實踐
【推薦】 MySQL多執行緒備份工具mydumper
【推薦】 Redux其實很簡單(原理篇)