上一篇博客,咱們介紹了ArrayBlockQueue,知道了它是基於數組實現的有界阻塞隊列,既然有基於數組實現的,那麼必定有基於鏈表實現的隊列了,沒錯,固然有,這就是咱們今天的主角:LinkedBlockingQueue。ArrayBlockQueue是有界的,那麼LinkedBlockingQueue是有界仍是無界的呢?我以爲能夠說是有界的,也能夠說是無界的,爲何這麼說呢?看下去你就知道了。node
和上篇博客同樣,咱們仍是先看下LinkedBlockingQueue的基本應用,而後解析LinkedBlockingQueue的核心代碼。數組
public static void main(String[] args) throws InterruptedException {
LinkedBlockingQueue<Integer> linkedBlockingQueue = new LinkedBlockingQueue();
linkedBlockingQueue.add(15);
linkedBlockingQueue.add(60);
linkedBlockingQueue.offer(50);
linkedBlockingQueue.put(100);
System.out.println(linkedBlockingQueue);
System.out.println(linkedBlockingQueue.size());
System.out.println(linkedBlockingQueue.take());
System.out.println(linkedBlockingQueue);
System.out.println(linkedBlockingQueue.poll());
System.out.println(linkedBlockingQueue);
System.out.println(linkedBlockingQueue.peek());
System.out.println(linkedBlockingQueue);
System.out.println(linkedBlockingQueue.remove(50));
System.out.println(linkedBlockingQueue);
}
複製代碼
運行結果:安全
[15, 60, 50, 100]
4
15
[60, 50, 100]
60
[50, 100]
50
[50, 100]
true
[100]
複製代碼
代碼比較簡單,先試着分析下:bash
代碼比較簡單,可是仍是有些細節不明白:源碼分析
要解決上面的疑問,最好的途徑仍是看源碼,下面咱們就來看看LinkedBlockingQueue的核心源碼。ui
LinkedBlockingQueue提供了三個構造方法,以下圖所示: this
咱們一個一個來分析。public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
複製代碼
無參的構造方法居然直接把「鍋」甩出去了,甩給了另一個構造方法,可是咱們要注意傳的參數:Integer.MAX_VALUE。spa
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
複製代碼
這個capacity是什麼呢?若是你們對代碼有必定的感受的話,應該很容易猜到這是LinkedBlockingQueue的最大容量。若是咱們調用無參的構造方法來建立LinkedBlockingQueue的話,那麼它的最大容量就是Integer.MAX_VALUE,咱們把它稱爲「無界」,可是咱們也能夠指定最大容量,那麼此隊列又是一個「有界」隊列了,因此有些博客很草率的說LinkedBlockingQueue是有界隊列,或者是無界隊列,我的認爲這是不嚴謹的。線程
咱們再來看看這個Node是個什麼鬼:指針
static class Node<E> {
E item;
Node<E> next;
Node(E x) { item = x; }
}
複製代碼
是否是有一種莫名的親切感,很明顯,這是單向鏈表的實現呀,next指向的就是下一個Node。
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);//調用第二個構造方法,傳入的capacity是Int的最大值,能夠說 是一個無界隊列。
final ReentrantLock putLock = this.putLock;
putLock.lock(); //開啓排他鎖
try {
int n = 0;//用於記錄LinkedBlockingQueue的size
//循環傳入的c集合
for (E e : c) {
if (e == null)//若是e==null,則拋出空指針異常
throw new NullPointerException();
if (n == capacity)//若是n==capacity,說明到了最大的容量,則拋出「Queue full」異常
throw new IllegalStateException("Queue full");
enqueue(new Node<E>(e));//入隊操做
++n;//n自增
}
count.set(n);//設置count
} finally {
putLock.unlock();//釋放排他鎖
}
}
複製代碼
public boolean offer(E e) {
if (e == null) throw new NullPointerException();//若是傳入的元素爲NULL,拋出異常
final AtomicInteger count = this.count;//取出count
if (count.get() == capacity)//若是count==capacity,說明到了最大容量,直接返回false
return false;
int c = -1;//表示size
Node<E> node = new Node<E>(e);//新建Node節點
final ReentrantLock putLock = this.putLock;
putLock.lock();//開啓排他鎖
try {
if (count.get() < capacity) {//若是count<capacity,說明尚未達到最大容量
enqueue(node);//入隊操做
c = count.getAndIncrement();//得到count,賦值給c後完成自增操做
if (c + 1 < capacity)//若是c+1 <capacity,說明還有剩餘的空間,喚醒由於調用notFull的await方法而被阻塞的線程
notFull.signal();
}
} finally {
putLock.unlock();//在finally中釋放排他鎖
}
if (c == 0)//若是c==0,說明釋放putLock的時候,隊列中有一個元素,則調用signalNotEmpty
signalNotEmpty();
return c >= 0;
}
複製代碼
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
複製代碼
代碼比較簡單,就是開啓排他鎖,喚醒由於調用notEmpty的await方法而被阻塞的線程,可是這裏須要注意,這裏得到的排他鎖已經再也不是putLock,而是takeLock。
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
複製代碼
add方法直接調用了offer方法,可是add和offer還不徹底同樣,當隊列滿了,若是調用offer方法,會直接返回false,可是調用add方法,會拋出"Queue full"的異常。
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();//若是傳入的元素爲NULL,拋出異常
int c = -1;//表示size
Node<E> node = new Node<E>(e);//新建Node節點
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;//得到count
putLock.lockInterruptibly();//開啓排他鎖
try {
//若是到了最大容量,調用notFull的await方法,等待喚醒,用while循環,是爲了防止虛假喚醒
while (count.get() == capacity) {
notFull.await();
}
enqueue(node);//入隊
c = count.getAndIncrement();//count先賦值給c後,再進行自增操做
if (c + 1 < capacity)//若是c+1<capacity,調用notFull的signal方法,喚醒由於調用notFull的await方法而被阻塞的線程
notFull.signal();
} finally {
putLock.unlock();//釋放排他鎖
}
if (c == 0)//若是隊列中有一個元素,喚醒由於調用notEmpty的await方法而被阻塞的線程
signalNotEmpty();
}
複製代碼
private void enqueue(Node<E> node) {
last = last.next = node;
}
複製代碼
入隊操做是否是特別簡單,就是把傳入的Node節點,賦值給last節點的next字段,再賦值給last字段,從而造成一個單向鏈表。
至此offer/add/put的核心源碼已經分析完畢,咱們來作一個小總結,offer/add/put都是添加元素的方法,不過他們之間仍是有所區別的,當隊列滿了,調用以上三個方法會出現不一樣的狀況:
public int size() {
return count.get();
}
複製代碼
沒什麼好說的,count記錄着LinkedBlockingQueue的size,得到後返回就是了。
public E take() throws InterruptedException {
E x;
int c = -1;//size
final AtomicInteger count = this.count;//得到count
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();//開啓排他鎖
try {
while (count.get() == 0) {//說明目前隊列中沒有數據
notEmpty.await();//阻塞,等待喚醒
}
x = dequeue();//出隊
c = count.getAndDecrement();//先賦值,後自減
if (c > 1)//若是size>1,說明在出隊以前,隊列中有至少兩個元素
notEmpty.signal();//喚醒由於調用notEmpty的await方法而被阻塞的線程
} finally {
takeLock.unlock();//釋放排他鎖
}
if (c == capacity)//若是隊列中還有一個剩餘空間
signalNotFull();
return x;
}
複製代碼
咱們再來看下signalNotFull方法:
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
複製代碼
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;
}
複製代碼
相比take方法,最大的區別就若是隊列爲空,執行take方法會阻塞當前線程,直到被喚醒,而poll方法,直接返回null。
public E peek() {
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
複製代碼
peek方法,只是拿到頭節點的值,可是不會移除該節點。
private E dequeue() {
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}
複製代碼
沒什麼好說的,就是彈出元素,而且移除彈出的元素。
至此take/poll/peek的核心源碼已經分析完畢,咱們來作一個小總結,take/poll/peek都是得到頭節點值的方法,不過他們之間仍是有所區別的:
LinkedBlockingQueue的核心源碼分析到這裏完畢了,謝謝你們。