《java.util.concurrent 包源碼閱讀》07 LinkedBlockingQueue

來源:互聯網
上載者:User

標籤:style   blog   color   java   使用   io   資料   for   

這篇文章來說說稍微複雜一些的LinkedBlockingQueue。LinkedBlockingQueue使用一個鏈表來實現,會有一個head和tail分別指向隊列的開始和隊列的結尾。因此LinkedBlockingQueue會有兩把鎖,分別控制這兩個元素,這樣在添加元素和拿走元素的時候就不會有鎖的衝突,因此取走元素操作的是head,而添加元素操作的是tail。

老規矩先看offer方法和poll方法

    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);        final ReentrantLock putLock = this.putLock;        putLock.lock();        try {            if (count.get() < capacity) {                enqueue(node);                c = count.getAndIncrement();                if (c + 1 < capacity)                    notFull.signal();            }        } finally {            putLock.unlock();        }        if (c == 0)            signalNotEmpty();        return c >= 0;    }

可以看到offer方法在添加元素時候僅僅涉及到putLock,但是還是會需要takeLock,看看signalNotEmpty代碼就知道。而poll方法拿走元素的時候涉及到takeLock,也是會需要putLock。參見signalNotFull()。關於signalNotEmpty會在後面講阻塞的時候講到。

    public E poll() {        final AtomicInteger count = this.count;        if (count.get() == 0)            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;    }

這裡順便說說隊列長度的count,因為有兩把鎖存在,所以如果還是像ArrayBlockingQueue一樣使用基本類型的count的話會同時用到兩把鎖,這樣就會很複雜,因此直接使用原子資料類型AtomicInteger來操作count。

接下來談談阻塞的問題,一個BlockingQueue會有兩個Condition:notFull和notEmpty,LinkedBlockingQueue會有兩把鎖,因此這兩個Condition肯定是由這兩個鎖分別建立的,takeLock建立notEmpty,putLock建立notFull。

    /** Lock held by take, poll, etc */    private final ReentrantLock takeLock = new ReentrantLock();    /** Wait queue for waiting takes */    private final Condition notEmpty = takeLock.newCondition();    /** Lock held by put, offer, etc */    private final ReentrantLock putLock = new ReentrantLock();    /** Wait queue for waiting puts */    private final Condition notFull = putLock.newCondition();

接下來看看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.        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.             */            while (count.get() == capacity) {                notFull.await();            }            enqueue(node);            c = count.getAndIncrement();            if (c + 1 < capacity)                notFull.signal();        } finally {            putLock.unlock();        }        if (c == 0)            signalNotEmpty();    }

其實大體邏輯和ArrayBlockingQueue差不多,也會需要通知notEmpty條件,因為notEmpty條件屬於takeLock,而調用signal方法需要擷取Lock,因此put方法也是用到了另外一個鎖:takeLock。這裡有一點會不同,按照道理來說put方法是不需要通知notFull條件的,是由由拿走元素的操作來通知的,但是notFull條件屬於putLock,而拿走元素時,是用了takeLock,因此這裡put方法在擁有putLock的情況通知notFull條件,會讓其他添加元素的方法避免過長時間的等待。同理對於take方法來說也通知notEmpty條件。

 

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

 

最後說說remove和contains方法,因為需要操作整個鏈表,因此需要同時擁有鎖才能操作。

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.