學習Java併發編程不得不去了解一下java.util.concurrent這個包,這個包下面有許多咱們常常用到的併發工具類,例如:ReentrantLock, CountDownLatch, CyclicBarrier, Semaphore等。而這些類的底層實現都依賴於AbstractQueuedSynchronizer這個類,因而可知這個類的重要性。因此在Java併發系列文章中我首先對AbstractQueuedSynchronizer這個類進行分析,因爲這個類比較重要,並且代碼比較長,爲了儘量分析的透徹一些,我決定用四篇文章對該類進行一個比較完整的介紹。本篇文章做爲概要介紹主要是讓讀者們對該類有個初步瞭解。爲了敘述簡單,後續有些地方會用AQS表明這個類。java
1. AbstractQueuedSynchronizer這個類是幹嗎的?node
相信要許多讀者使用過ReentrantLock,可是殊不知道AbstractQueuedSynchronizer的存在。其實ReentrantLock實現了一個內部類Sync,該內部類繼承了AbstractQueuedSynchronizer,全部鎖機制的實現都是依賴於Sync內部類,也能夠說ReentrantLock的實現就是依賴於AbstractQueuedSynchronizer類。於此相似,CountDownLatch, CyclicBarrier, Semaphore這些類也是採用一樣的方式來實現本身對於鎖的控制。可見,AbstractQueuedSynchronizer是這些類的基石。那麼AQS內部到底實現了什麼以致於因此這些類都要依賴於它呢?能夠這樣說,AQS爲這些類提供了基礎設施,也就是提供了一個密碼鎖,這些類擁有了密碼鎖以後能夠本身來設置密碼鎖的密碼。此外,AQS還提供了一個排隊區,而且提供了一個線程訓導員,咱們知道線程就像一個原始的野蠻人,它不懂得講禮貌,它只會橫衝直撞,因此你得一步一步去教它,告訴它何時須要去排隊了,要到哪裏去排隊,排隊前要作些什麼,排隊後要作些什麼。這些教化工做所有都由AQS幫你完成了,從它這裏教化出來的線程都變的很是文明懂禮貌,再也不是原始的野蠻人,因此之後咱們只須要和這些文明的線程打交道就好了,千萬不要和原始線程有過多的接觸!編程
2. 爲什麼說AbstractQueuedSynchronizer提供了一把密碼鎖?併發
1 //同步隊列的頭結點 2 private transient volatile Node head; 3 4 //同步隊列的尾結點 5 private transient volatile Node tail; 6 7 //同步狀態 8 private volatile int state; 9 10 //獲取同步狀態 11 protected final int getState() { 12 return state; 13 } 14 15 //設置同步狀態 16 protected final void setState(int newState) { 17 state = newState; 18 } 19 20 //以CAS方式設置同步狀態 21 protected final boolean compareAndSetState(int expect, int update) { 22 return unsafe.compareAndSwapInt(this, stateOffset, expect, update); 23 }
上面的代碼列出了AQS的全部成員變量,能夠看到AQS的成員變量只有三個,分別是同步隊列頭結點引用,同步隊列尾結點引用以及同步狀態。注意,這三個成員變量都使用了volatile關鍵字進行修飾,這就確保了多個線程對它的修改都是內存可見的。整個類的核心就是這個同步狀態,能夠看到同步狀態其實就是一個int型的變量,你們能夠把這個同步狀態當作一個密碼鎖,並且仍是從房間裏面鎖起來的密碼鎖,state具體的值就至關於密碼控制着密碼鎖的開合。固然這個鎖的密碼是多少就由各個子類來規定了,例如在ReentrantLock中,state等於0表示鎖是開的,state大於0表示鎖是鎖着的,而在Semaphore中,state大於0表示鎖是開的,state等於0表示鎖是鎖着的。工具
2. AbstractQueuedSynchronizer的排隊區是怎樣實現的?學習
AbstractQueuedSynchronizer內部其實有兩個排隊區,一個是同步隊列,一個是條件隊列。從上圖能夠看出,同步隊列只有一條,而條件隊列能夠有多條。同步隊列的結點分別持有先後結點的引用,而條件隊列的結點只有一個指向後繼結點的引用。圖中T表示線程,每一個結點包含一個線程,線程在獲取鎖失敗後首先進入同步隊列排隊,而想要進入條件隊列該線程必須持有鎖才行。接下來咱們看看隊列中每一個結點的結構。this
1 //同步隊列的結點 2 static final class Node { 3 4 static final Node SHARED = new Node(); //表示當前線程以共享模式持有鎖 5 6 static final Node EXCLUSIVE = null; //表示當前線程以獨佔模式持有鎖 7 8 static final int CANCELLED = 1; //表示當前結點已經取消獲取鎖 9 10 static final int SIGNAL = -1; //表示後繼結點的線程須要運行 11 12 static final int CONDITION = -2; //表示當前結點在條件隊列中排隊 13 14 static final int PROPAGATE = -3; //表示後繼結點能夠直接獲取鎖 15 16 volatile int waitStatus; //表示當前結點的等待狀態 17 18 volatile Node prev; //表示同步隊列中的前繼結點 19 20 volatile Node next; //表示同步隊列中的後繼結點 21 22 volatile Thread thread; //當前結點持有的線程引用 23 24 Node nextWaiter; //表示條件隊列中的後繼結點 25 26 //當前結點狀態是不是共享模式 27 final boolean isShared() { 28 return nextWaiter == SHARED; 29 } 30 31 //返回當前結點的前繼結點 32 final Node predecessor() throws NullPointerException { 33 Node p = prev; 34 if (p == null) { 35 throw new NullPointerException(); 36 } else { 37 return p; 38 } 39 } 40 41 //構造器1 42 Node() {} 43 44 //構造器2, 默認用這個構造器 45 Node(Thread thread, Node mode) { 46 //注意持有模式是賦值給nextWaiter 47 this.nextWaiter = mode; 48 this.thread = thread; 49 } 50 51 //構造器3, 只在條件隊列中用到 52 Node(Thread thread, int waitStatus) { 53 this.waitStatus = waitStatus; 54 this.thread = thread; 55 } 56 }
Node表明同步隊列和條件隊列中的一個結點,它是AbstractQueuedSynchronizer的內部類。Node有不少屬性,好比持有模式,等待狀態,同步隊列中的前繼和後繼,以及條件隊列中的後繼引用等等。能夠把同步隊列和條件隊列當作是排隊區,每一個結點當作是排隊區的座位,將線程當作是排隊的客人。客人剛來時會先去敲敲門,看看鎖有沒有開,若是鎖沒開它就會去排隊區領取一個號碼牌,聲明本身想要以什麼樣的方式來持有鎖,最後再到隊列的末尾進行排隊。spa
3. 怎樣理解獨佔模式和共享模式?線程
前面講到每一個客人在排隊前會領取一個號碼牌,聲明本身想要以什麼樣的方式來佔有鎖,佔有鎖的方式分爲獨佔模式和共享模式,那麼怎樣來理解獨佔模式和共享模式呢?實在找不到什麼好的比喻,你們能夠聯想一下公共廁所,獨佔模式的人比較霸道,老子要麼就不進,進來了就不準別人再進了,本身一我的獨自佔用整個廁所。共享模式的人就沒那麼講究了,當它發現這個廁所已經能夠用了以後,它本身進來還不算,還得熱心的問下後面的人介不介意一塊兒用,若是後面的人不介意一塊兒使用那就不用再排隊了你們一塊兒上就是了, 固然若是後面的人介意那就只好留在隊列裏繼續排隊了。code
4. 怎樣理解結點的等待狀態?
咱們還看到每一個結點都有一個等待狀態,這個等待狀態分爲CANCELLED,SIGNAL,CONDITION,PROPAGATE四種狀態。能夠將這個等待狀態看做是掛在座位旁邊的牌子,標識當前座位上的人的等待狀態。這個牌子的狀態不只本身能夠修改,其餘人也能夠修改。例如當這個線程在排隊過程當中已經打算放棄了,它就會將本身座位上的牌子設置爲CANCELLED,這樣其餘人看到了就能夠將它清理出隊列。還有一種狀況是,當線程在座位上要睡着以前,它怕本身睡過了頭,就會將前面位置上的牌子改成SIGNAL,由於每一個人在離開隊列前都會回到本身座位上看一眼,若是看到牌子上狀態爲SIGNAL,它就會去喚醒下一我的。只有保證前面位置上的牌子爲SIGNAL,當前線程纔會安心的睡去。CONDITION狀態表示該線程在條件隊列中排隊,PROPAGATE狀態提醒後面來的線程能夠直接獲取鎖,這個狀態只在共享模式用到,後面單獨講共享模式的時候會講到。
5. 結點進入同步隊列時會進行哪些操做?
1 //結點入隊操做, 返回前一個結點 2 private Node enq(final Node node) { 3 for (;;) { 4 //獲取同步隊列尾結點引用 5 Node t = tail; 6 //若是尾結點爲空說明同步隊列尚未初始化 7 if (t == null) { 8 //初始化同步隊列 9 if (compareAndSetHead(new Node())) { 10 tail = head; 11 } 12 } else { 13 //1.指向當前尾結點 14 node.prev = t; 15 //2.設置當前結點爲尾結點 16 if (compareAndSetTail(t, node)) { 17 //3.將舊的尾結點的後繼指向新的尾結點 18 t.next = node; 19 //for循環惟一的出口 20 return t; 21 } 22 } 23 } 24 }
注意,入隊操做使用一個死循環,只有成功將結點添加到同步隊列尾部纔會返回,返回結果是同步隊列原先的尾結點。下圖演示了整個操做過程。
讀者須要注意添加尾結點的順序,分爲三步:指向尾結點,CAS更改尾結點,將舊尾結點的後繼指向當前結點。在併發環境中這三步操做不必定能保證完成,因此在清空同步隊列全部已取消的結點這一操做中,爲了尋找非取消狀態的結點,不是從前向後遍歷而是從後向前遍歷的。還有就是每一個結點進入隊列中時它的等待狀態是爲0,只有後繼結點的線程須要掛起時纔會將前面結點的等待狀態改成SIGNAL。
注:以上所有分析基於JDK1.7,不一樣版本間會有差別,讀者須要注意