深入研究JDK併發集合類-LinkedBlockingQueue 中put和take阻塞原理分析

目錄

 

結構:

PUT操作

TAKE操作


結構:

public class LinkedBlockingQueue<E> extends AbstractQueue<E>

        implements BlockingQueue<E>, java.io.Serializable {

    private final int capacity;//queue的容量大小

    private final AtomicInteger count = new AtomicInteger(0);//當前存儲的數量

    private transient Node<E> head;//Head of linked list.

    private transient Node<E> last;//Tail of linked list.

    private final ReentrantLock takeLock = new ReentrantLock();//取出鎖

    private final Condition notEmpty = takeLock.newCondition(); /** Wait queue for waiting takes */

    private final ReentrantLock putLock = new ReentrantLock();//放入鎖

    private final Condition notFull = putLock.newCondition();/** Wait queue for waiting puts */

   原文:https://blog.csdn.net/lan861698789/article/details/81323750

    static class Node<E> {

        E item;

        Node<E> next;

        Node(E x) {

          item = x;

        }

    }

}

分析:

結構包含:容量大小、存儲數量大小、頭節點、尾節點、put和take鎖

這裏的Node和linkedlist裏的Node不同,這裏只有next節點,沒有父節點。

擁有兩把鎖,這是take和put不相互影響的關鍵。

   原文:https://blog.csdn.net/lan861698789/article/details/81323750

PUT操作

public void put(E e) throws InterruptedException {

    int c = -1;

    Node<E> node = new Node(e);

    final ReentrantLock putLock = this.putLock;

    final AtomicInteger count = this.count;

    putLock.lockInterruptibly();

    try {

        while (count.get() == capacity) {//如果大小和容量一樣了,表示不能放入了,要阻塞。

//While用的比if好多了。萬一消費者GET時候,喚醒了多個。

            notFull.await();//當前線程休息,等待signal隨機喚醒。

        }

        last = last.next = node;//賦值新元素的最後一個位置

        c = count.getAndIncrement();//大小+1

        if (c + 1 < capacity)//如果當前大小還是小於容量,則繼續喚醒一個put線程

            notFull.signal();

    } finally {

        putLock.unlock();

    }

    if (c == 0) {//此時隊列中總共有1node了。因爲初始值是0Put後會增加getAndIncrement

        signalNotEmpty();//通知take消費者,可以消費

    }

}

   原文:https://blog.csdn.net/lan861698789/article/details/81323750

private void signalNotEmpty() {

    final ReentrantLock takeLock = this.takeLock;

    takeLock.lock();

    try {

        notEmpty.signal();

    } finally {

        takeLock.unlock();

    }

}

分析:

流程:

1.判斷是否能生產放入

2.如果不能生產,則釋放當前put鎖,線程阻塞等待。

3.如果能生產,則向隊列尾部新增元素。

注意:

極端情況下當生產者都阻塞等待了,哪裏來喚醒?

可以看take操作,在take後,會判斷取操作之前的大小和容器大小一樣,則signal一個生產者,讓他繼續工作。這個被喚醒的生產者會繼續喚醒其他的生產者。

if (c == capacity) {

    signalNotFull();//通知一個生產者,可以生產了

}      

   原文:https://blog.csdn.net/lan861698789/article/details/81323750

TAKE操作

public E take() throws InterruptedException {

    E x;

    int c = -1;

    final AtomicInteger count = this.count;

    final ReentrantLock takeLock = this.takeLock;

    takeLock.lockInterruptibly();//lockInterruptibly()方法是一個可以對中斷進行響應的鎖申請動作

    try {

        while (count.get() == 0) {//如果大小=0,表示不能取了,要阻塞

            notEmpty.await();

        }

        //取出頭元素

        Node<E> h = head;

        Node<E> first = h.next;

        h.next = h; // help GC

        head = first;

        x = first.item;

        first.item = null;

       

        c = count.getAndDecrement();//先賦值,再減1

        if (c > 1)

            notEmpty.signal();

    } finally {

        takeLock.unlock();

    }

    if (c == capacity) {

        signalNotFull();//通知一個生產者,可以生產了

    }      

    return x;

}

private void signalNotFull() {

    final ReentrantLock putLock = this.putLock;

    putLock.lock();

    try {

        notFull.signal();//通知生產者

    } finally {

        putLock.unlock();

    }

}

分析:

流程:

1.判斷是否能消費取出

2.能,則從隊列尾部取

3.不能,則釋放take鎖,阻塞等待被喚醒。

   原文:https://blog.csdn.net/lan861698789/article/details/81323750

注意:

極端情況下當消費者都阻塞等待了,哪裏來喚醒?

可以看put操作,在put後,會判斷取操作之前的大小=0,則signal一個消費者,讓他繼續工作,這個被喚醒的消費着會繼續喚醒其他的消費者。

if (c == 0) {

    signalNotEmpty();//通知take消費者,可以消費

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章