九渤昌、并發(fā)隊列之LinkedBlockingQueue源碼解析

之前我們已經了解過雙向同步隊列了恒傻,LinkedBlockingQueu有一點不同淑际,使用了兩把鎖畏纲,所以相對也更復雜一點


類結構

我們依然是線看其整個類內部機構:

  • final int capacity; // 隊列的容量
  • final AtomicInteger count = new AtomicInteger(); // 當前元素的數量
  • Node<E> head; // 頭結點
  • Node<E> last; // 尾節(jié)點
  • ReentrantLock takeLock = new ReentrantLock(); // 出隊鎖
  • Condition notEmpty = takeLock.newCondition(); // 出隊條件變量
  • ReentrantLock putLock = new ReentrantLock(); // 入隊鎖
  • Condition notFull = putLock.newCondition(); // 入隊條件變量
    static class Node<E> {   // 就是一個單向鏈表
        E item;
        Node<E> next;
        Node(E x) { item = x; }
    }
    

由上可以LinkedBlockingQueue使用了兩把鎖,為什么這么做呢庸追,為何不像雙向隊列那樣只使用一把鎖兩個條件變量呢霍骄?
我們帶著疑問往下看。

方法介紹

和雙向隊列一樣淡溯,只要是隊列读整,離不開offer、poll咱娶、peek這幾個重要方法米间。

入隊

其入隊操作也有兩種put、offer膘侮、add

  • offer
    public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        final AtomicInteger count = this.count;
        if (count.get() == capacity)
            return false;
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        putLock.lock();         // 獲取到鎖屈糊,其他入隊操作將被阻塞
        try {
            if (count.get() < capacity) {
                enqueue(node);          // 未滿則加入隊列
                c = count.getAndIncrement();    // 并增加隊列數量
                if (c + 1 < capacity)
                    notFull.signal();       // 如果還沒滿,繼續(xù)喚醒其他入隊線程
            }
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();   // c==0說明之前隊列時空的琼了,那么入隊之后就不空了逻锐,因此喚醒出隊線程,但為什么是==0,而不是>= 0呢雕薪?
        return c >= 0;
    }
    public boolean add(E e) {   // add 方法繼承自父類昧诱,調用的是offer方法
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
    
  • put
    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();  // 響應中斷
        try {
            while (count.get() == capacity) {  // 已經滿了,則等待直到可以入隊
                notFull.await();
            }
            enqueue(node);          // 入隊
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }
    

上面代碼要點:

  1. 入隊操作使用 putLock鎖所袁,也就是說不不會阻塞出隊操作
  2. 入隊成功且如果隊列還是未滿盏档,則釋放之前因為隊列滿了而阻塞的入隊線程
  3. put和offer的區(qū)別之一:put操作是一個阻塞操作,如果隊列滿則會等待直到可以插入燥爷,所以put的返回值是void永不失敗蜈亩,而offer的返回值是boolean,可能因為隊列滿了而失敗
  4. put和offer的區(qū)別之二:put會響應中斷
  5. 回到上面那個問題前翎,為什么c==0 的時候才喚醒出隊線程呢稚配?

    這是因為如果c=0,則表示在這次入隊之前隊列時空的港华,那么所有的出隊操作都將被阻塞药有。這個時候需要入隊線程來喚醒。那么為什么c>0的時候不喚醒?其實這個問題我們應該拋開之前講過的雙向隊列的思路愤惰,LinkedBlockingDeque是使用一個鎖同時控制讀寫,所以讀寫互相通知赘理,但這里不一樣宦言,用了兩把鎖。再往上看代碼notFull.signal(),發(fā)現了嗎商模,寫線程會通知寫線程奠旺。這就會大大提升性能,讀和寫互補干擾施流,只有在隊列為空响疚,或者隊列滿時才有交流。

可以看出put和offer的不同之處在于瞪醋,put是一定會成功的忿晕,而offer則因為隊列滿了而失敗。

出隊

同入隊方式相對應也有三種方式poll银受、take践盼、remove,remove也是父類的方法宾巍,調用的是子類的poll方法咕幻。

  • poll
    public E poll() {
        final AtomicInteger count = this.count;
        if (count.get() == 0)           // 如果隊列為空,返回null
            return null;
        E x = null;
        int c = -1;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            if (count.get() > 0) {
                x = dequeue();  // 出隊
                c = count.getAndDecrement();
                if (c > 1)
                    notEmpty.signal();  // 通知下一個出隊線程
            }
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();        // 如果之前滿了顶霞,則釋放一個入隊操作肄程。
        return x;
    }
    

take()方法與put方法類似,相對poll选浑,會阻塞直到成功蓝厌,不會返回null,且響應中斷鲜侥。代碼這里不再展示

獲取元素

public E peek() {
    if (count.get() == 0)
        return null;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lock();
    try {
        Node<E> first = head.next;
        if (first == null)
            return null;
        else
            return first.item;
    } finally {
        takeLock.unlock();
    }
}

從代碼可以看出邏輯非常簡單褂始,就是takelock來實現并發(fā)阻塞出隊操作。

其他方法

  • size
    public int size() {
        return count.get(); // 這是一個原子類描函,所以是一個精確值
    }
    
  • contains
    public boolean contains(Object o) {
        if (o == null) return false;
        fullyLock();        // 讀寫鎖同時鎖住崎苗,所以也是一個精確值
        try {
            for (Node<E> p = head.next; p != null; p = p.next)
                if (o.equals(p.item))
                    return true;
            return false;
        } finally {
            fullyUnlock();
        }
    }
    

將這兩個函數,主要是與ConcurrentLinkedQueue作對比舀寓,ConcurrentLinkedQueue采用的非阻塞的方式實現了入隊出隊的操作胆数,但其size()和contains()方法并沒有實現同步,因此不精確互墓,但是入隊出隊的效率更高必尼。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子判莉,更是在濱河造成了極大的恐慌豆挽,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件券盅,死亡現場離奇詭異帮哈,居然都是意外死亡,警方通過查閱死者的電腦和手機锰镀,發(fā)現死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門娘侍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人泳炉,你說我怎么就攤上這事憾筏。” “怎么了花鹅?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵氧腰,是天一觀的道長。 經常有香客問我翠胰,道長容贝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任之景,我火速辦了婚禮腺占,結果婚禮上跷跪,老公的妹妹穿的比我還像新娘劲适。我一直安慰自己狰住,他們只是感情好,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布轻纪。 她就那樣靜靜地躺著油额,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刻帚。 梳的紋絲不亂的頭發(fā)上潦嘶,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音崇众,去河邊找鬼掂僵。 笑死,一個胖子當著我的面吹牛顷歌,可吹牛的內容都是我干的锰蓬。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼眯漩,長吁一口氣:“原來是場噩夢啊……” “哼芹扭!你這毒婦竟也來了麻顶?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤舱卡,失蹤者是張志新(化名)和其女友劉穎辅肾,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體轮锥,經...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡宛瞄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了交胚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡盈电,死狀恐怖蝴簇,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情匆帚,我是刑警寧澤熬词,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站吸重,受9級特大地震影響互拾,放射性物質發(fā)生泄漏。R本人自食惡果不足惜嚎幸,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一颜矿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嫉晶,春花似錦骑疆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至椎镣,卻和暖如春诈火,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背状答。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工冷守, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人剪况。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓教沾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親译断。 傳聞我的和親對象是個殘疾皇子授翻,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354