現(xiàn)在是2020/05/18:23:12分,是的,馬上就要到凌晨了,然而我才開始寫這篇文章,為什么這么晚寫這篇文章,不困嗎,或許是,或許不是,其實在我刷完抖音之后腦海里想的就是分析一下LinkedBlockingQueue的源碼吧,至于為什么要這么晚還去分析,有這個必要嗎,或許是自己喜歡這個點有點久了。
一般你們遇到的每一篇文章都是經(jīng)過我最少一個周之前想寫的內(nèi)容,所以文章出現(xiàn)的時候,我自己在心里也沉淀了很久才發(fā)出來,這樣就會比較對自己友好一點。
是的,是人都會有私心,我喜歡分享,但我不會將很私密的事情去分享,因為互聯(lián)網(wǎng)上什么人都有,沒有必要將自己喜歡的東西分享出來,文章只會分享一些技術(shù)相關(guān)的內(nèi)容和自己思考的一些事情,所以你看到的內(nèi)容也就是我喜歡分享的內(nèi)容,好了,閑扯就不說了,接下來我要開始分析LinkedBlockingQueue了。
按照自己的風格,接下來先看下LinkedBlockingQueue的繼承接口和實現(xiàn)接口吧。
public class LinkedBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {}
java中的繼承,封裝,多態(tài)都在源碼里面體現(xiàn)的比較友好,所以這里就不過多介紹了,想了解這些內(nèi)容的可以去搜索了解一下,一般我們創(chuàng)建一個容器都是從new關(guān)鍵字開始的,容器的大小一般支持自定義的方式。
public LinkedBlockingQueue() {this(Integer.MAX_VALUE);}
當我們手動new一個容器時,初始大小分配好了,這就是整形數(shù)值的最大值,即默認開辟了這么大的數(shù)組空間,即內(nèi)存空間,是不是有點浪費呢?自己思考思考。
public LinkedBlockingQueue(int capacity) {if (capacity <= 0) throw new IllegalArgumentException();this.capacity = capacity;last = head = new Node<E>(null);}
其實,我們也可以自己手動容器的大小,,但是不能小于等于0,這里就進行了前置校驗,拋出非法異常,然后將初始值賦值給數(shù)組容量,因為數(shù)據(jù)是存放在節(jié)點當中的,所以這里就暫時將new Node()的值設(shè)置為了null。
我們使用容器要干什么?裝填數(shù)據(jù)唄,就是常用的offer(),put()方法,這里我們先介紹一下put()方法的源碼吧。
public void put(E e) throws InterruptedException {????????if?(e?==?null)?throw?new?NullPointerException();????????//若裝填的數(shù)據(jù)為null,直接拋出空指針異常int c = -1;Node<E> node = new Node<E>(e);????????final?ReentrantLock?putLock?=?this.putLock;//獲取putLock鎖????????final?AtomicInteger?count?=?this.count;//獲取當前隊列的元素個數(shù)????????putLock.lockInterruptibly();//這個鎖是可中斷的????????try?{????????//這句話就是表明put方法是一個阻塞性的方法while (count.get() == capacity) {notFull.await();}????????????//如隊列操作enqueue(node);c = count.getAndIncrement();if (c + 1 < capacity)notFull.signal();} finally {????????//最后在finally語句塊進行釋放鎖putLock.unlock();}if (c == 0)signalNotEmpty();}
首先這是一個線程安全的方法,為什么這么說呢?看到lock了沒,加鎖了,同一時刻只能有一個線程進行操作,保證了多線程下共享數(shù)據(jù)的安全。
LinkedBlockingQueue不允許隊列的元素為null,所以,這里在入隊列之前就進行了前置校驗,若元素e為空,則直接拋出NPE異常,阻斷程序的運行,在入隊列之前先判斷隊列是否已滿,若已滿則等待,這也是為什么之前有的面試官總是會問到put()方法和其它添加元素方法的區(qū)別,是不是看過源碼之后一目了然。
private void enqueue(Node<E> node) {last = last.next = node;}
入隊列很簡單,就是將新增節(jié)點數(shù)據(jù)直接連接到隊列的尾部,這樣就保證了隊列的先入先出特點。
好了,put()方法的過程到這里就結(jié)束了,接下來我們在看下其它方法吧。
就暫時先看下take()方法的實現(xiàn)過程吧,首先這個take()方法也是一個阻塞性方法,隊列若沒有數(shù)據(jù),則直接等待。
public E take() throws InterruptedException {E x;int c = -1;????????final?AtomicInteger?count?=?this.count;//獲取當前隊列元素的個數(shù)????????final?ReentrantLock?takeLock?=?this.takeLock;//獲取takeLock鎖takeLock.lockInterruptibly();//進行加鎖操作try {????????????while?(count.get()?==?0)?{//判斷隊列的元素個數(shù)是否為0,若為0則等待notEmpty.await();}????????????x?=?dequeue();//出隊列操作c = count.getAndDecrement();if (c > 1)notEmpty.signal();} finally {????????//釋放鎖操作takeLock.unlock();}if (c == capacity)signalNotFull();return x;}
首先判斷當前隊列的元素個數(shù)是否為0,若為0,則等待,這里就基于condition的await()方法實現(xiàn)的等待機制。然后就是dequeue()方法了,
?private?E?dequeue()?{Node<E> h = head;Node<E> first = h.next;h.next = h; // help GChead = first;E x = first.item;????????first.item?=?null;return x;}
出隊列就將數(shù)據(jù)置為null,便于GC進行不可用數(shù)據(jù)的回收,其實在你看這部分時了解一下jvm是很必要的,一般隊列和棧都很詳細,就是我想獲取隊列的隊首元素,但是我又不想讓它出隊列,這樣就提供了peek()方法。
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;elsereturn first.item;} finally {takeLock.unlock();}}
這里,首先判斷隊列的元素個數(shù)是否為0,若為0,則直接返回null,否則,加鎖,然后獲取??隊列隊首元素,這樣就獲取了你想要的隊首元素,關(guān)于這部分,其實你理解了什么是鎖,什么是原子類,這個分析過程和分析ArrayList源碼基本上一樣,沒有看過我的源碼分析的請歷史文章進行查找。
其實熟悉我的文章的讀者都知道我分析集合容器時總是喜歡分析它的isEmpty()方法和clear()方法,因為我覺得這個對于我理解集合太重要了。
public void clear() {fullyLock();try {for (Node<E> p, h = head; (p = h.next) != null; h = p) {h.next = h;p.item = null;}????????????head?=?last;if (count.getAndSet(0) == capacity)notFull.signal();} finally {fullyUnlock();}}
循環(huán)迭代隊列的元素,然后將其置為null,等待GC進行數(shù)據(jù)的回收,這樣隊列就清空了,然后將表示隊列元素個數(shù)清零就可以了,整個過程的加鎖和釋放就不過多介紹了,到這里差不多隊列的內(nèi)容就介紹完了,方法太多,我這里就簡單介紹了一部分方法,其它方法大差不差沒有什么區(qū)別,所里這里就不過多介紹了,
public int size() {return count.get();}
這個count是原子的,即使是多線程操作下也能保證原子性。差點忘了分析這個方法了,判斷元素是否在隊列里。
public boolean contains(Object o) {if (o == null) return false;fullyLock();try {for (Node<E> p = head.next; p != null; p = p.next)if (o.equals(p.item))return true;return false;} finally {fullyUnlock();}}
循環(huán)迭代隊列里的元素值,與其進行比較,若相等則返回true,否則返回false,時間復雜度為O(n),不了解時間復雜度,這里不再繼續(xù)說了,需要的可以自行了解。
最后,在看下如何轉(zhuǎn)為數(shù)組的方法吧,這個方法比較特殊,因為它沒有調(diào)用底層的拷貝,利用的確實新開辟了一個數(shù)組,然后將數(shù)組進行填充到新的數(shù)組里面。
public Object[] toArray() {fullyLock();try {int size = count.get();Object[] a = new Object[size];int k = 0;for (Node<E> p = head.next; p != null; p = p.next)a[k++] = p.item;return a;} finally {fullyUnlock();}}
整個過程也是加鎖和釋放鎖的過程,所以這就是一個線程安全的容器類,到這里就結(jié)束了,時間也不早了,0:02了,好了,喜歡的內(nèi)容到這里點個在看唄,到這里就結(jié)束了,后面想要分享內(nèi)容再繼續(xù)分享。
我喜歡分享,你喜歡閱讀@WwpwW