在上一遍線程池的講解中,如果線程的大小大於核心線程的大小,就會放到緩衝隊列裏面,這個隊列就是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實現。
紙上得來終覺淺,絕知此事要躬行。