Java並發系列[1]----AbstractQueuedSynchronizer源碼分析之概要分析

來源:互聯網
上載者:User

標籤:expec   基於   col   status   隊列   密碼   gets   exclusive   簡單   

學習Java並發編程不得不去瞭解一下java.util.concurrent這個包,這個包下面有許多我們經常用到的並發工具類,例如:ReentrantLock, CountDownLatch, CyclicBarrier, Semaphore等。而這些類的底層實現都依賴於AbstractQueuedSynchronizer這個類,由此可見這個類的重要性。所以在Java並發系列文章中我首先對AbstractQueuedSynchronizer這個類進行分析,由於這個類比較重要,而且代碼比較長,為了儘可能分析的透徹一些,我決定用四篇文章對該類進行一個比較完整的介紹。本篇文章作為概要介紹主要是讓讀者們對該類有個初步瞭解。為了敘述簡單,後續有些地方會用AQS代表這個類。

1. AbstractQueuedSynchronizer這個類是幹嘛的?

相信要許多讀者使用過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表示線程,每個結點包含一個線程,線程在擷取鎖失敗後首先進入同步隊列排隊,而想要進入條件隊列該線程必須持有鎖才行。接下來我們看看隊列中每個結點的結構。

 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     //構造器142     Node() {}43     44     //構造器2, 預設用這個構造器45     Node(Thread thread, Node mode) {46         //注意持有模式是賦值給nextWaiter47         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有很多屬性,比如持有模式,等待狀態,同步隊列中的前繼和後繼,以及條件隊列中的後繼引用等等。可以把同步隊列和條件隊列看成是排隊區,每個結點看成是排隊區的座位,將線程看成是排隊的客人。客人剛來時會先去敲敲門,看看鎖有沒有開,如果鎖沒開它就會去排隊區領取一個號碼牌,聲明自己想要以什麼樣的方式來持有鎖,最後再到隊列的末尾進行排隊。

3. 怎樣理解獨佔模式和共用模式?

前面講到每個客人在排隊前會領取一個號碼牌,聲明自己想要以什麼樣的方式來佔有鎖,佔有鎖的方式分為獨佔模式和共用模式,那麼怎樣來理解獨佔模式和共用模式呢?實在找不到什麼好的比喻,大家可以聯想一下公用廁所,獨佔模式的人比較霸道,老子要麼就不進,進來了就不許別人再進了,自己一個人獨自佔用整個廁所。共用模式的人就沒那麼講究了,當它發現這個廁所已經可以用了之後,它自己進來還不算,還得熱心的問下後面的人介不介意一起用,如果後面的人不介意一起使用那就不用再排隊了大家一起上就是了, 當然如果後面的人介意那就只好留在隊列裡繼續排隊了。

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,不同版本間會有差異,讀者需要注意

Java並發系列[1]----AbstractQueuedSynchronizer源碼分析之概要分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.