源碼解讀--LinkedBlockingDeque講解

   在上一遍線程池的講解中,如果線程的大小大於核心線程的大小,就會放到緩衝隊列裏面,這個隊列就是LinkedBlockingDeque。下面就來深入講解一下這個隊列。java源碼系列

一.LinkedBlockingDequ的定義

首先看下圖譜


它是一個隊列,而且還是阻塞式的,還可以告訴你,它是線程安全的。LinkedBlockingDeque用的是一個雙向的鏈表,Node的聲明如下:

static final class Node<E> {
    E item;  //當前的值
    Node<E> prev;  //指向前驅節點
    Node<E> next;  //指向後繼節點
    Node(E x) { 
        item = x;
    }
}

使用兩個條件來實現阻塞

//從隊列中拿取值的條件判斷,如果隊列是空的話,有線程再來拿值的話,這個線程要被阻塞,直到有值被添加了,值才能被別人拿走
private final Condition notEmpty = lock.newCondition();

//往隊列裏面添加值,隊列滿了了,需要阻塞,等到別的線程把隊列裏面的值拿走纔可以添加值
private final Condition notFull = lock.newCondition();

這種阻塞就是典型的生產者--消費者模式,比如去聽老教授講課,教室裏面的座位就只有那麼多,座位坐滿了,裏面來的學生就需要站着,如果有坐着的學生離開座位,接下來旁邊的同學就可以坐下。

LinkedBlockingDeque鎖使用的獨佔鎖ReentrantLock,這個鎖就不細講了。知道他是獨佔,別的線程等待就可以了。

二.LinkedBlockingDeque的顯現

2.1 初始化

public LinkedBlockingDeque() {  //無界? 不指定最大是int的最大值
    this(Integer.MAX_VALUE);
}
public LinkedBlockingDeque(int capacity) {  //指定隊列的大小,好處是可以防止過度擴張
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
}
public LinkedBlockingDeque(Collection<? extends E> c) {
    this(Integer.MAX_VALUE);
    final ReentrantLock lock = this.lock;
    lock.lock(); //獲取到鎖
    try {
        for (E e : c) {
            if (e == null)
                throw new NullPointerException();
            if (!linkLast(new Node<E>(e)))   //將集合裏面的值一個一個的添加進去,如果隊列滿了,就會拋出Deque full的異常
                throw new IllegalStateException("Deque full");
        }
    } finally {
        lock.unlock();
    }
}

2.2 添加隊列值

  

public void put(E e) throws InterruptedException {
    putLast(e);
}

public void putLast(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();  //如果添加的數據爲空,拋出空指針異常
    Node<E> node = new Node<E>(e);  //初始數據節點
    final ReentrantLock lock = this.lock;
    lock.lock();  //上鎖
    try {
        while (!linkLast(node))  //將節點放到隊列的隊尾
            notFull.await(); /如果隊列滿了的話,需要阻塞
    } finally {
        lock.unlock();
    }
}
private boolean linkLast(Node<E> node) {
    // assert lock.isHeldByCurrentThread();
    if (count >= capacity)  //超出了容量,需要阻塞
        return false;
    Node<E> l = last;  //記錄last節點
    node.prev = l;  //當前node的前驅指向last
    last = node;  last指向node
    if (first == null) //如果第一個爲空,node就是第一個
        first = node;
    else
        l.next = node;  //最前一個last的next指針指向現在的node
    ++count;  //隊列裏面的值加 1
    notEmpty.signal();  //釋放資源,其他線程可以添加值
    return true;
}
2.3 獲取隊列值

public E take() throws InterruptedException {
    return takeFirst();
}
public E takeFirst() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E x;
        while ( (x = unlinkFirst()) == null)  //獲取隊列的值,如果第一個值爲null,需要等其他線程把值添加進去,它才能獲取到值
            notEmpty.await();
        return x;
    } finally {
        lock.unlock();
    }
}
private E unlinkFirst() {  //出隊列,移除第一個值
    // assert lock.isHeldByCurrentThread();
    Node<E> f = first;
    if (f == null)  //如果第一個值爲空
        return null;
    Node<E> n = f.next; //n 指向第一個節點的下一個
    E item = f.item;  //拿到第一個節點裏面的值
    f.item = null; //將第一個節點裏面的值賦值爲空,方便gc
    f.next = f; //之前第一個節點現在後繼節點已經沒有了,指向了自身,沒有別的節點有對它的引用,會被gc線程清除
    first = n;  //現在第一個節點指向了之前第二個節點n
    if (n == null)  //如果第二個節點是null
        last = null;  //尾指針指向null
    else
        n.prev = null;  //否則的話,之前第二個的節點的前驅指針指向null
    --count;  //隊列的數據減一
    notFull.signal();  //之前獲取數據沒有獲取成功的線程現在可以拿取數據了
    return item;
}

三.總結

看完了LinkedBlockingDeque的源碼實現是不是感覺很簡單。它首先是一個雙向鏈表,可以支持先進先出。其次線程安全,用的是ReentranLock。接着是阻塞式的,用的是notFull,notEmptyl兩個Condition實現。

紙上得來終覺淺,絕知此事要躬行。

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