并發(fā)編程(五):LinkedBlockingQueue源碼解析

1.1 簡介

LinkedBlockingQueue是一個由鏈表結(jié)構(gòu)組成的有界阻塞隊列皂贩,此隊列是FIFO(先進先出)的順序來訪問的蝌矛,它由隊尾插入后再從隊頭取出或移除碎罚,其中隊列的頭部是在隊列中時間最長的元素欣硼,隊列的尾部是在隊列中時間最短的元素混卵。在LinkedBlockingQueue類中分別用2個不同的鎖takeLock映穗、putLock來保護隊頭和隊尾操作。如下圖所示:

image

1.2 類圖

image

1.3 源碼分析

1.3.1 屬性與鏈表節(jié)點類

//鏈表節(jié)點類幕随,next指向下一個節(jié)點蚁滋。如果下一個節(jié)點時null表示沒有節(jié)點了。  
static class Node<E> {  
    E item;  
  
    Node<E> next;  
  
    Node(E x) { item = x; }  
}  
  
// 最大容量上限赘淮,默認是 Integer.MAX_VALUE  
private final int capacity;  
  
// 當前元素數(shù)量辕录,這是個原子類。  
private final AtomicInteger count = new AtomicInteger(0);  
  
// 頭結(jié)點  
private transient Node<E> head;  
  
// 尾結(jié)點  
private transient Node<E> last;  
  
// 隊頭訪問鎖  
private final ReentrantLock takeLock = new ReentrantLock();  
  
// 隊頭訪問等待條件梢卸、隊列  
private final Condition notEmpty = takeLock.newCondition();  
  
// 隊尾訪問鎖  
private final ReentrantLock putLock = new ReentrantLock();  
  
// 隊尾訪問等待條件走诞、隊列  
private final Condition notFull = putLock.newCondition();  

使用原子類AtomicInteger是因為讀寫分別使用了不同的鎖,但都會訪問這個屬性來計算隊列中元素的數(shù)量蛤高,所以它需要是線程安全的速梗。關(guān)AtomicInteger詳細請看我的這一篇文章:【Java并發(fā)編程】深入分析AtomicInteger(二)

1.3.2 offer操作

public boolean offer(E e) {  
    if (e == null) throw new NullPointerException();  
    final AtomicInteger count = this.count;  
    //當隊列滿時,直接返回了false襟齿,沒有被阻塞等待元素插入  
    if (count.get() == capacity)  
        return false;  
    int c = -1;  
    Node<E> node = new Node(e);  
    //開啟隊尾保護鎖  
    final ReentrantLock putLock = this.putLock;  
    putLock.lock();  
    try {  
        if (count.get() < capacity) {  
            enqueue(node);  
            //原則計數(shù)類  
            c = count.getAndIncrement();  
            if (c + 1 < capacity)  
                notFull.signal();  
        }  
    } finally {  
        //釋放鎖  
        putLock.unlock();  
    }  
    if (c == 0)  
        signalNotEmpty();  
    return c >= 0;  
}  
  
//在持有鎖下指向下一個節(jié)點  
private void enqueue(Node<E> node) {  
    // assert putLock.isHeldByCurrentThread();  
    // assert last.next == null;  
    last = last.next = node;  
}  

1.3.3 put操作

//put 操作把指定元素添加到隊尾姻锁,如果沒有空間則一直等待。  
public void put(E e) throws InterruptedException {  
    if (e == null) throw new NullPointerException();  
    // Note: convention in all put/take/etc is to preset local var  
    // holding count negative to indicate failure unless set.  
    //注釋:在所有的 put/take/etc等操作中變量c為負數(shù)表示失敗猜欺,>=0表示成功位隶。  
    int c = -1;  
    Node<E> node = new Node(e);  
    final ReentrantLock putLock = this.putLock;  
    final AtomicInteger count = this.count;  
    putLock.lockInterruptibly();  
    try {  
        /* 
         * Note that count is used in wait guard even though it is 
         * not protected by lock. This works because count can 
         * only decrease at this point (all other puts are shut 
         * out by lock), and we (or some other waiting put) are 
         * signalled if it ever changes from capacity. Similarly 
         * for all other uses of count in other wait guards. 
         */  
        /* 
         * 注意,count用于等待監(jiān)視开皿,即使它沒有用鎖保護涧黄。這個可行是因為 
         * count 只能在此刻(持有putLock)減小(其他put線程都被鎖拒之門外)赋荆, 
         * 當count對capacity發(fā)生變化時笋妥,當前線程(或其他put等待線程)將被通知。 
         * 在其他等待監(jiān)視的使用中也類似窄潭。 
         */  
        while (count.get() == capacity) {  
            notFull.await();  
        }  
        enqueue(node);  
        c = count.getAndIncrement();  
        // 還有可添加空間則喚醒put等待線程春宣。  
        if (c + 1 < capacity)  
            notFull.signal();  
    } finally {  
        putLock.unlock();  
    }  
    if (c == 0)  
        signalNotEmpty();  
}  

1.3.4 take操作

//彈出隊頭元素,如果沒有會被阻塞直到元素返回  
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.await();//沒有元素一直阻塞  
        }  
        x = dequeue();  
        c = count.getAndDecrement();  
        if (c > 1)//如果還有可獲取元素嫉你,喚醒等待獲取的線程月帝。  
          notEmpty.signal();  
    } finally {  
        //拿到元素后釋放鎖  
        takeLock.unlock();  
    }  
    if (c == capacity)  
        signalNotFull();  
    return x;  
}  
  
//在持有鎖下返回隊列隊頭第一個節(jié)點  
private E dequeue() {  
    // assert takeLock.isHeldByCurrentThread();  
    // assert head.item == null;  
    Node<E> h = head;  
    Node<E> first = h.next;  
    h.next = h; // help GC  
    //出隊后的節(jié)點作為頭節(jié)點并將元素置空  
    head = first;  
    E x = first.item;  
    first.item = null;  
    return x;  
}  

1.3.5 remove操作

image
//移除指定元素。  
public boolean remove(Object o) {  
    if (o == null) return false;  
    //對兩把鎖加鎖  
    fullyLock();  
    try {  
        for (Node<E> trail = head, p = trail.next;  
                p != null;  
                trail = p, p = p.next) {  
            if (o.equals(p.item)) {  
                unlink(p, trail);  
                return true;  
            }  
        }  
        return false;  
    } finally {  
        fullyUnlock();  
    }  
}  
  
//p是移除元素所在節(jié)點幽污,trail是移除元素的上一個節(jié)點  
void unlink(Node<E> p, Node<E> trail) {  
    // assert isFullyLocked();  
    // p.next is not changed, to allow iterators that are  
    // traversing p to maintain their weak-consistency guarantee.  
    p.item = null;  
    //將trail下一個節(jié)點指向p的下一個節(jié)點  
    trail.next = p.next;  
    if (last == p)  
        last = trail;  
    if (count.getAndDecrement() == capacity)  
        notFull.signal();  
}  
  
void fullyLock() {  
    putLock.lock();  
    takeLock.lock();  
}  
  
//釋放鎖時確保和加鎖順序一致  
void fullyUnlock() {  
    takeLock.unlock();  
    putLock.unlock();  
}  

注意嚷辅,鎖的釋放順序與加鎖順序是相反的。

作者:小毛驢距误,一個Java游戲服務(wù)器開發(fā)者 原文地址:https://liulongling.github.io/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末簸搞,一起剝皮案震驚了整個濱河市扁位,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌趁俊,老刑警劉巖贤牛,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異则酝,居然都是意外死亡殉簸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門沽讹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來般卑,“玉大人,你說我怎么就攤上這事爽雄◎鸺欤” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵挚瘟,是天一觀的道長叹谁。 經(jīng)常有香客問我,道長乘盖,這世上最難降的妖魔是什么焰檩? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮订框,結(jié)果婚禮上析苫,老公的妹妹穿的比我還像新娘。我一直安慰自己穿扳,他們只是感情好衩侥,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著矛物,像睡著了一般茫死。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上履羞,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天峦萎,我揣著相機與錄音,去河邊找鬼吧雹。 笑死骨杂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的雄卷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼蛤售,長吁一口氣:“原來是場噩夢啊……” “哼丁鹉!你這毒婦竟也來了妒潭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤揣钦,失蹤者是張志新(化名)和其女友劉穎雳灾,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冯凹,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡谎亩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了宇姚。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匈庭。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖浑劳,靈堂內(nèi)的尸體忽然破棺而出阱持,到底是詐尸還是另有隱情,我是刑警寧澤魔熏,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布衷咽,位于F島的核電站,受9級特大地震影響蒜绽,放射性物質(zhì)發(fā)生泄漏镶骗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一躲雅、第九天 我趴在偏房一處隱蔽的房頂上張望卖词。 院中可真熱鬧,春花似錦吏夯、人聲如沸此蜈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽裆赵。三九已至,卻和暖如春跺嗽,著一層夾襖步出監(jiān)牢的瞬間战授,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工桨嫁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留植兰,地道東北人。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓璃吧,卻偏偏與公主長得像楣导,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子畜挨,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容