學(xué)習(xí)Java并發(fā)編程不得不去了解一下java.util.concurrent這個(gè)包,這個(gè)包下面有許多我們經(jīng)常用到的并發(fā)工具類,例如:ReentrantLock, CountDownLatch, CyclicBarrier, Semaphore等。而這些類的底層實(shí)現(xiàn)都依賴于AbstractQueuedSynchronizer這個(gè)類,由此可見這個(gè)類的重要性。所以在Java并發(fā)系列文章中我首先對(duì)AbstractQueuedSynchronizer這個(gè)類進(jìn)行分析,由于這個(gè)類比較重要,而且代碼比較長(zhǎng),為了盡可能分析的透徹一些,我決定用四篇文章對(duì)該類進(jìn)行一個(gè)比較完整的介紹。本篇文章作為概要介紹主要是讓讀者們對(duì)該類有個(gè)初步了解。為了敘述簡(jiǎn)單,后續(xù)有些地方會(huì)用AQS代表這個(gè)類。
1. AbstractQueuedSynchronizer這個(gè)類是干嘛的?
相信要許多讀者使用過(guò)ReentrantLock,但是卻不知道AbstractQueuedSynchronizer的存在。其實(shí)ReentrantLock實(shí)現(xiàn)了一個(gè)內(nèi)部類Sync,該內(nèi)部類繼承了AbstractQueuedSynchronizer,所有鎖機(jī)制的實(shí)現(xiàn)都是依賴于Sync內(nèi)部類,也可以說(shuō)ReentrantLock的實(shí)現(xiàn)就是依賴于AbstractQueuedSynchronizer類。于此類似,CountDownLatch, CyclicBarrier, Semaphore這些類也是采用同樣的方式來(lái)實(shí)現(xiàn)自己對(duì)于鎖的控制。可見,AbstractQueuedSynchronizer是這些類的基石。那么AQS內(nèi)部到底實(shí)現(xiàn)了什么以至于所以這些類都要依賴于它呢?可以這樣說(shuō),AQS為這些類提供了基礎(chǔ)設(shè)施,也就是提供了一個(gè)密碼鎖,這些類擁有了密碼鎖之后可以自己來(lái)設(shè)置密碼鎖的密碼。此外,AQS還提供了一個(gè)排隊(duì)區(qū),并且提供了一個(gè)線程訓(xùn)導(dǎo)員,我們知道線程就像一個(gè)原始的野蠻人,它不懂得講禮貌,它只會(huì)橫沖直撞,所以你得一步一步去教它,告訴它什么時(shí)候需要去排隊(duì)了,要到哪里去排隊(duì),排隊(duì)前要做些什么,排隊(duì)后要做些什么。這些教化工作全部都由AQS幫你完成了,從它這里教化出來(lái)的線程都變的非常文明懂禮貌,不再是原始的野蠻人,所以以后我們只需要和這些文明的線程打交道就行了,千萬(wàn)不要和原始線程有過(guò)多的接觸!
2. 為何說(shuō)AbstractQueuedSynchronizer提供了一把密碼鎖?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
//同步隊(duì)列的頭結(jié)點(diǎn) private transient volatile Node head; //同步隊(duì)列的尾結(jié)點(diǎn) private transient volatile Node tail; //同步狀態(tài) private volatile int state; //獲取同步狀態(tài) protected final int getState() { return state; } //設(shè)置同步狀態(tài) protected final void setState( int newState) { state = newState; } //以CAS方式設(shè)置同步狀態(tài) protected final boolean compareAndSetState( int expect, int update) { return unsafe.compareAndSwapInt( this , stateOffset, expect, update); } |
上面的代碼列出了AQS的所有成員變量,可以看到AQS的成員變量只有三個(gè),分別是同步隊(duì)列頭結(jié)點(diǎn)引用,同步隊(duì)列尾結(jié)點(diǎn)引用以及同步狀態(tài)。注意,這三個(gè)成員變量都使用了volatile關(guān)鍵字進(jìn)行修飾,這就確保了多個(gè)線程對(duì)它的修改都是內(nèi)存可見的。整個(gè)類的核心就是這個(gè)同步狀態(tài),可以看到同步狀態(tài)其實(shí)就是一個(gè)int型的變量,大家可以把這個(gè)同步狀態(tài)看成一個(gè)密碼鎖,而且還是從房間里面鎖起來(lái)的密碼鎖,state具體的值就相當(dāng)于密碼控制著密碼鎖的開合。當(dāng)然這個(gè)鎖的密碼是多少就由各個(gè)子類來(lái)規(guī)定了,例如在ReentrantLock中,state等于0表示鎖是開的,state大于0表示鎖是鎖著的,而在Semaphore中,state大于0表示鎖是開的,state等于0表示鎖是鎖著的。
3. AbstractQueuedSynchronizer的排隊(duì)區(qū)是怎樣實(shí)現(xiàn)的?
AbstractQueuedSynchronizer內(nèi)部其實(shí)有兩個(gè)排隊(duì)區(qū),一個(gè)是同步隊(duì)列,一個(gè)是條件隊(duì)列。從上圖可以看出,同步隊(duì)列只有一條,而條件隊(duì)列可以有多條。同步隊(duì)列的結(jié)點(diǎn)分別持有前后結(jié)點(diǎn)的引用,而條件隊(duì)列的結(jié)點(diǎn)只有一個(gè)指向后繼結(jié)點(diǎn)的引用。圖中T表示線程,每個(gè)結(jié)點(diǎn)包含一個(gè)線程,線程在獲取鎖失敗后首先進(jìn)入同步隊(duì)列排隊(duì),而想要進(jìn)入條件隊(duì)列該線程必須持有鎖才行。接下來(lái)我們看看隊(duì)列中每個(gè)結(jié)點(diǎn)的結(jié)構(gòu)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
//同步隊(duì)列的結(jié)點(diǎn) static final class Node { static final Node SHARED = new Node(); //表示當(dāng)前線程以共享模式持有鎖 static final Node EXCLUSIVE = null ; //表示當(dāng)前線程以獨(dú)占模式持有鎖 static final int CANCELLED = 1 ; //表示當(dāng)前結(jié)點(diǎn)已經(jīng)取消獲取鎖 static final int SIGNAL = - 1 ; //表示后繼結(jié)點(diǎn)的線程需要運(yùn)行 static final int CONDITION = - 2 ; //表示當(dāng)前結(jié)點(diǎn)在條件隊(duì)列中排隊(duì) static final int PROPAGATE = - 3 ; //表示后繼結(jié)點(diǎn)可以直接獲取鎖 volatile int waitStatus; //表示當(dāng)前結(jié)點(diǎn)的等待狀態(tài) volatile Node prev; //表示同步隊(duì)列中的前繼結(jié)點(diǎn) volatile Node next; //表示同步隊(duì)列中的后繼結(jié)點(diǎn) volatile Thread thread; //當(dāng)前結(jié)點(diǎn)持有的線程引用 Node nextWaiter; //表示條件隊(duì)列中的后繼結(jié)點(diǎn) //當(dāng)前結(jié)點(diǎn)狀態(tài)是否是共享模式 final boolean isShared() { return nextWaiter == SHARED; } //返回當(dāng)前結(jié)點(diǎn)的前繼結(jié)點(diǎn) final Node predecessor() throws NullPointerException { Node p = prev; if (p == null ) { throw new NullPointerException(); } else { return p; } } //構(gòu)造器1 Node() {} //構(gòu)造器2, 默認(rèn)用這個(gè)構(gòu)造器 Node(Thread thread, Node mode) { //注意持有模式是賦值給nextWaiter this .nextWaiter = mode; this .thread = thread; } //構(gòu)造器3, 只在條件隊(duì)列中用到 Node(Thread thread, int waitStatus) { this .waitStatus = waitStatus; this .thread = thread; } } |
Node代表同步隊(duì)列和條件隊(duì)列中的一個(gè)結(jié)點(diǎn),它是AbstractQueuedSynchronizer的內(nèi)部類。Node有很多屬性,比如持有模式,等待狀態(tài),同步隊(duì)列中的前繼和后繼,以及條件隊(duì)列中的后繼引用等等。可以把同步隊(duì)列和條件隊(duì)列看成是排隊(duì)區(qū),每個(gè)結(jié)點(diǎn)看成是排隊(duì)區(qū)的座位,將線程看成是排隊(duì)的客人。客人剛來(lái)時(shí)會(huì)先去敲敲門,看看鎖有沒有開,如果鎖沒開它就會(huì)去排隊(duì)區(qū)領(lǐng)取一個(gè)號(hào)碼牌,聲明自己想要以什么樣的方式來(lái)持有鎖,最后再到隊(duì)列的末尾進(jìn)行排隊(duì)。
4 怎樣理解獨(dú)占模式和共享模式?
前面講到每個(gè)客人在排隊(duì)前會(huì)領(lǐng)取一個(gè)號(hào)碼牌,聲明自己想要以什么樣的方式來(lái)占有鎖,占有鎖的方式分為獨(dú)占模式和共享模式,那么怎樣來(lái)理解獨(dú)占模式和共享模式呢?實(shí)在找不到什么好的比喻,大家可以聯(lián)想一下公共廁所,獨(dú)占模式的人比較霸道,老子要么就不進(jìn),進(jìn)來(lái)了就不許別人再進(jìn)了,自己一個(gè)人獨(dú)自占用整個(gè)廁所。共享模式的人就沒那么講究了,當(dāng)它發(fā)現(xiàn)這個(gè)廁所已經(jīng)可以用了之后,它自己進(jìn)來(lái)還不算,還得熱心的問下后面的人介不介意一起用,如果后面的人不介意一起使用那就不用再排隊(duì)了大家一起上就是了, 當(dāng)然如果后面的人介意那就只好留在隊(duì)列里繼續(xù)排隊(duì)了。
5 怎樣理解結(jié)點(diǎn)的等待狀態(tài)?
我們還看到每個(gè)結(jié)點(diǎn)都有一個(gè)等待狀態(tài),這個(gè)等待狀態(tài)分為CANCELLED,SIGNAL,CONDITION,PROPAGATE四種狀態(tài)。可以將這個(gè)等待狀態(tài)看作是掛在座位旁邊的牌子,標(biāo)識(shí)當(dāng)前座位上的人的等待狀態(tài)。這個(gè)牌子的狀態(tài)不僅自己可以修改,其他人也可以修改。例如當(dāng)這個(gè)線程在排隊(duì)過(guò)程中已經(jīng)打算放棄了,它就會(huì)將自己座位上的牌子設(shè)置為CANCELLED,這樣其他人看到了就可以將它清理出隊(duì)列。還有一種情況是,當(dāng)線程在座位上要睡著之前,它怕自己睡過(guò)了頭,就會(huì)將前面位置上的牌子改為SIGNAL,因?yàn)槊總€(gè)人在離開隊(duì)列前都會(huì)回到自己座位上看一眼,如果看到牌子上狀態(tài)為SIGNAL,它就會(huì)去喚醒下一個(gè)人。只有保證前面位置上的牌子為SIGNAL,當(dāng)前線程才會(huì)安心的睡去。CONDITION狀態(tài)表示該線程在條件隊(duì)列中排隊(duì),PROPAGATE狀態(tài)提醒后面來(lái)的線程可以直接獲取鎖,這個(gè)狀態(tài)只在共享模式用到,后面單獨(dú)講共享模式的時(shí)候會(huì)講到。
6. 結(jié)點(diǎn)進(jìn)入同步隊(duì)列時(shí)會(huì)進(jìn)行哪些操作?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
//結(jié)點(diǎn)入隊(duì)操作, 返回前一個(gè)結(jié)點(diǎn) private Node enq( final Node node) { for (;;) { //獲取同步隊(duì)列尾結(jié)點(diǎn)引用 Node t = tail; //如果尾結(jié)點(diǎn)為空說(shuō)明同步隊(duì)列還沒有初始化 if (t == null ) { //初始化同步隊(duì)列 if (compareAndSetHead( new Node())) { tail = head; } } else { //1.指向當(dāng)前尾結(jié)點(diǎn) node.prev = t; //2.設(shè)置當(dāng)前結(jié)點(diǎn)為尾結(jié)點(diǎn) if (compareAndSetTail(t, node)) { //3.將舊的尾結(jié)點(diǎn)的后繼指向新的尾結(jié)點(diǎn) t.next = node; //for循環(huán)唯一的出口 return t; } } } } |
注意,入隊(duì)操作使用一個(gè)死循環(huán),只有成功將結(jié)點(diǎn)添加到同步隊(duì)列尾部才會(huì)返回,返回結(jié)果是同步隊(duì)列原先的尾結(jié)點(diǎn)。下圖演示了整個(gè)操作過(guò)程。
讀者需要注意添加尾結(jié)點(diǎn)的順序,分為三步:指向尾結(jié)點(diǎn),CAS更改尾結(jié)點(diǎn),將舊尾結(jié)點(diǎn)的后繼指向當(dāng)前結(jié)點(diǎn)。在并發(fā)環(huán)境中這三步操作不一定能保證完成,所以在清空同步隊(duì)列所有已取消的結(jié)點(diǎn)這一操作中,為了尋找非取消狀態(tài)的結(jié)點(diǎn),不是從前向后遍歷而是從后向前遍歷的。還有就是每個(gè)結(jié)點(diǎn)進(jìn)入隊(duì)列中時(shí)它的等待狀態(tài)是為0,只有后繼結(jié)點(diǎn)的線程需要掛起時(shí)才會(huì)將前面結(jié)點(diǎn)的等待狀態(tài)改為SIGNAL。
注:以上全部分析基于JDK1.7,不同版本間會(huì)有差異,讀者需要注意。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:http://www.cnblogs.com/liuyun1995/p/8400663.html