并發(fā)編程之java.util.concurrent(一)

本篇不寫前言,直接扒衣服!

1:concurrent包結(jié)構(gòu)

圖1 concurrent自底向上

最底層:

volatile變量:volatile保證變量在內(nèi)存中的可見(jiàn)性。java線程模型包括線程的私有內(nèi)存和所有線程的公共內(nèi)存,這與cpu緩存和內(nèi)存類似。線程對(duì)變量進(jìn)行操作的時(shí)候一般是從公共內(nèi)存中拷貝變量的副本,等修改完之后重新寫入到公共內(nèi)存,這存在并發(fā)風(fēng)險(xiǎn)。而被volatile標(biāo)注的變量通過(guò)CPU原語(yǔ)保證了變量的內(nèi)存可見(jiàn)性,也就是說(shuō)一個(gè)線程讀到工作內(nèi)存中的變量一定是其他線程已經(jīng)更新到內(nèi)存中的變量。為簡(jiǎn)單起見(jiàn),你可以想象成所有線程都在公共內(nèi)存中操作volatile變量,這樣一個(gè)線程對(duì)volatile的更改其他線程都看的見(jiàn),即內(nèi)存可見(jiàn)性!

CAS:compare and swap,比較-相等-替換新值,跟底層CPU有關(guān),一般CAS操作的都是volatile變量。CAS操作包含內(nèi)存位置(V)、預(yù)期原值(A)和新值(B),?如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值 。否則,處理器不做任何操作。如方法a.CAS(b,c),如果內(nèi)存中a與預(yù)期值b相等,那么把a(bǔ)更新成c。cas操作在java中由sun.misc.Unsafe類全權(quán)代理了!

最底層結(jié):concurrent包中用cas死循環(huán)修改volatile變量直到修改成功是最常見(jiàn)的手法!

第二層:

AtomicXXX類:常用基本類型和引用都有Atomic實(shí)現(xiàn),其中最重要的兩點(diǎn)當(dāng)然是一個(gè)volatile變量和一個(gè)CAS操作。++i和i++操作就是volatile和cas的典型用法,incrementAndget和getAndincrement是兩個(gè)實(shí)現(xiàn)方法,死循環(huán):首先獲取volatile變量值a,然后執(zhí)行a.cas(a,a+1)直到修改成功。

AQS框架:總有面試官會(huì)問(wèn)你AQS框架,盡管我懷疑他們并不真正了解所有細(xì)節(jié),Doug lea最屌的思想應(yīng)該都在AQS框架里了,直接上圖(借用,侵刪)

該類最主要的兩部分就是state狀態(tài)和Node節(jié)點(diǎn)(即隊(duì)列中的元素),前一個(gè)是資源狀態(tài),后一個(gè)還是cas操作volatile的典型應(yīng)用,Node還分為獨(dú)占模式和共享模式

第二層源碼解析:

以獨(dú)占模式為例,想象醫(yī)院某診室排隊(duì)看病。

A:如何競(jìng)爭(zhēng)資源(去看病)?

public final void acquire(int arg) {

? ? ? ? if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

? ? ? ? ? ? selfInterrupt();

}

這是競(jìng)爭(zhēng)資源的入口,不管有多少線程、是富得流油還是窮的吃土都要從這進(jìn)!

B:獲取資源后發(fā)生了啥?

首先,嘗試獲取資源,成true敗false,此方法每個(gè)子類有自己的實(shí)現(xiàn),就像有的醫(yī)院需要排隊(duì)(公平鎖),有的可以花錢不用排隊(duì)(非公平鎖)。

protected boolean tryAcquire(int arg) {

? ? ? ? throw new UnsupportedOperationException();

}

然后,為當(dāng)前線程創(chuàng)建節(jié)點(diǎn)Node并設(shè)置成獨(dú)占模式(相當(dāng)于每個(gè)看病的人取了一個(gè)號(hào),并且是獨(dú)占診室的那種號(hào)),先用快速方法加入隊(duì)尾,加入失敗的話調(diào)用enq方法死循環(huán)加入,總之要保證每個(gè)病人都取到了號(hào),并且進(jìn)入排隊(duì)狀態(tài)。

private Node addWaiter(Node mode) {

? ? ? ? Node node = new Node(Thread.currentThread(), mode);

? ? ? ? // 快速加入隊(duì)尾,如果失敗就調(diào)用enq方法死循環(huán)加入

? ? ? ? Node pred = tail;

? ? ? ? if (pred != null) {

? ? ? ? ? ? node.prev = pred;

? ? ? ? ? ? if (compareAndSetTail(pred, node)) {

? ? ? ? ? ? ? ? pred.next = node;

? ? ? ? ? ? ? ? return node;

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? enq(node);

? ? ? ? return node;

? ? }

private Node enq(final Node node) {

? ? ? ? //CAS外加死循環(huán)操作volatile的Node節(jié)點(diǎn),這個(gè)套路我們太熟悉了,保證了隊(duì)尾競(jìng)爭(zhēng)的線程安全

? ? ? ? for (;;) {

? ? ? ? ? ? Node t = tail;

? ? ? ? ? ? if (t == null) { // Must initialize

? ? ? ? ? ? ? ? if (compareAndSetHead(new Node()))

? ? ? ? ? ? ? ? ? ? tail = head;

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? node.prev = t;

? ? ? ? ? ? ? ? if (compareAndSetTail(t, node)) {

? ? ? ? ? ? ? ? ? ? t.next = node;

? ? ? ? ? ? ? ? ? ? return t;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? }

再次,線程不能一直等待,需要休息,于是線程輪詢查看前驅(qū)節(jié)點(diǎn)是否占用了資源,如果是那么自己嘗試獲取資源,獲取到了就把自己設(shè)置為頭結(jié)點(diǎn)。如果不是,那么沒(méi)關(guān)系,調(diào)用shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()兩個(gè)方法。

final boolean acquireQueued(final Node node, int arg) {

? ? ? ? boolean failed = true;

? ? ? ? try {

? ? ? ? ? ? boolean interrupted = false;

? ? ? ? ? ? for (;;) {

? ? ? ? ? ? ? ? final Node p = node.predecessor();

? ? ? ? ? ? ? ? if (p == head && tryAcquire(arg)) {

? ? ? ? ? ? ? ? ? ? setHead(node);

? ? ? ? ? ? ? ? ? ? p.next = null; // 幫助垃圾回收

? ? ? ? ? ? ? ? ? ? failed = false;

? ? ? ? ? ? ? ? ? ? return interrupted;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())

? ? ? ? ? ? ? ? ? ? interrupted = true;

? ? ? ? ? ? }

? ? ? ? } finally {

? ? ? ? ? ? if (failed)

? ? ? ? ? ? ? ? cancelAcquire(node);

? ? ? ? }

? ? }

然后,假如當(dāng)前線程前驅(qū)節(jié)點(diǎn)沒(méi)有占用資源或者人家占用資源還沒(méi)釋放,那么調(diào)用shouldParkAfterFailedAcquire(p, node)方法

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {

? ? ? ? int ws = pred.waitStatus;

? ? ? ? if (ws == Node.SIGNAL)

? ? ? ? ? ? return true;

? ? ? ? if (ws > 0) {

? ? ? ? ? ? do {

? ? ? ? ? ? ? ? node.prev = pred = pred.prev;

? ? ? ? ? ? } while (pred.waitStatus > 0);

? ? ? ? ? ? pred.next = node;

? ? ? ? } else {

? ? ? ? ? ? compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

? ? ? ? }

? ? ? ? return false;

? ? }

圖3 waitStatus幾個(gè)值機(jī)器表示

看上面代碼,ws是當(dāng)前線程前驅(qū)節(jié)點(diǎn)的狀態(tài),如果前驅(qū)節(jié)點(diǎn)狀態(tài)是SIGNAL,意思是后繼節(jié)點(diǎn)應(yīng)該等待,那么返回true。否則,如果前驅(qū)節(jié)點(diǎn)狀態(tài)大于0,意思就是取消了(不排隊(duì)了)那么就移動(dòng)到不排隊(duì)的節(jié)點(diǎn)前面去,不排隊(duì)的線程最后會(huì)被垃圾回收器回收。如果前驅(qū)節(jié)點(diǎn)狀態(tài)是其他,那么就把前驅(qū)節(jié)點(diǎn)狀態(tài)設(shè)置成SIGNAL,告訴他當(dāng)前線程需要被等待。以上過(guò)程執(zhí)行完當(dāng)前線程就可以等待了parkAndCheckInterrupt()(跟前面排隊(duì)的病人溝通,告訴他我要休息一下,快叫到我的時(shí)候通知我一下)。

private final boolean parkAndCheckInterrupt() {

? ? ? ? LockSupport.park(this);

? ? ? ? return Thread.interrupted();

? ? }

最后,可見(jiàn)這里面的等待實(shí)際上是調(diào)用LockSupport.park方法實(shí)現(xiàn)的,這里的park實(shí)際上與wait方法類似。

C:有借有還,再借不難,釋放資源

public final boolean release(int arg) {

? ? ? ? if (tryRelease(arg)) {

? ? ? ? ? ? Node h = head;

? ? ? ? ? ? if (h != null && h.waitStatus != 0)

? ? ? ? ? ? ? ? unparkSuccessor(h);

? ? ? ? ? ? return true;

? ? ? ? }

? ? ? ? return false;

? ? }

首先,釋放資源入口是release方法,release實(shí)際上與acquire恰好鏡像,如tryRelease可以自己實(shí)現(xiàn),然后真正釋放的時(shí)候?qū)嶋H上調(diào)用了unparkSuccessor(h)方法

private void unparkSuccessor(Node node) {

? ? ? ? int ws = node.waitStatus;

? ? ? ? if (ws < 0)

? ? ? ? ? ? compareAndSetWaitStatus(node, ws, 0);

? ? ? ? Node s = node.next;

? ? ? ? if (s == null || s.waitStatus > 0) {

? ? ? ? ? ? s = null;

? ? ? ? ? ? for (Node t = tail; t != null && t != node; t = t.prev)

? ? ? ? ? ? ? ? if (t.waitStatus <= 0)

? ? ? ? ? ? ? ? ? ? s = t;

? ? ? ? }

? ? ? ? if (s != null)

? ? ? ? ? ? LockSupport.unpark(s.thread);

? ? }

解除占用方法,可以看到,首先將占用資源的線程節(jié)點(diǎn)狀態(tài)由設(shè)置成0,然后調(diào)用LockSupport.unpark方法喚醒next節(jié)點(diǎn)的線程,相當(dāng)于await/signal方法中的signal。

獨(dú)占模式說(shuō)完下面分析下共享模式。

A:如何競(jìng)爭(zhēng)資源(想想排隊(duì)上廁所)?

猜也能猜個(gè)大概,肯定是acquireShare巴拉巴拉的。

public final void acquireShared(int arg) {

? ? ? ? if (tryAcquireShared(arg) < 0)

? ? ? ? ? ? doAcquireShared(arg);

? ? }

上面代碼是獲取共享資源的,首先嘗試獲取,這個(gè)與獨(dú)占模式類似,稍有不同的地方是其返回值,-1表示獲取失敗,0表示獲取成功但沒(méi)有可用資源,1表示獲取成功并且有資源。-1表示廁所門都沒(méi)進(jìn)去、0表示進(jìn)廁所門了但是沒(méi)有坑位、1表示即進(jìn)入了廁所門又有坑位。

重點(diǎn):共享資源可以有多份,這樣可以同時(shí)讓多個(gè)線程共享資源,所以PROPAGATE狀態(tài)可以保證在一個(gè)線程釋放資源后其他狀態(tài)為PROPAGATE的線程都能被喚醒。

B:怎么競(jìng)爭(zhēng)資源?

private void doAcquireShared(int arg) {

? ? ? ? final Node node = addWaiter(Node.SHARED);

? ? ? ? boolean failed = true;

? ? ? ? try {

? ? ? ? ? ? boolean interrupted = false;

? ? ? ? ? ? for (;;) {

? ? ? ? ? ? ? ? final Node p = node.predecessor();

? ? ? ? ? ? ? ? if (p == head) {

? ? ? ? ? ? ? ? ? ? int r = tryAcquireShared(arg);

? ? ? ? ? ? ? ? ? ? if (r >= 0) {

? ? ? ? ? ? ? ? ? ? ? ? setHeadAndPropagate(node, r);

? ? ? ? ? ? ? ? ? ? ? ? p.next = null; // help GC

? ? ? ? ? ? ? ? ? ? ? ? if (interrupted)

? ? ? ? ? ? ? ? ? ? ? ? ? ? selfInterrupt();

? ? ? ? ? ? ? ? ? ? ? ? failed = false;

? ? ? ? ? ? ? ? ? ? ? ? return;

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? if (shouldParkAfterFailedAcquire(p, node) &&

? ? ? ? ? ? ? ? ? ? parkAndCheckInterrupt())

? ? ? ? ? ? ? ? ? ? interrupted = true;

? ? ? ? ? ? }

? ? ? ? } finally {

? ? ? ? ? ? if (failed)

? ? ? ? ? ? ? ? cancelAcquire(node);

? ? ? ? }

? ? }

上述代碼在嘗試獲取資源失敗后進(jìn)入(即沒(méi)進(jìn)入廁所門就進(jìn)入該代碼塊)。addWaiter獨(dú)占模式中已經(jīng)講過(guò)了,先快速入隊(duì),快速失敗后enq死循環(huán)cas操作隊(duì)尾入隊(duì),只不過(guò)這里設(shè)置的模式使共享而已。然后看死循環(huán)里面的內(nèi)容,是不是太熟悉了?過(guò)程實(shí)際上是一樣的,先看下排在自己前面的人是不是占著坑位,如果是就嘗試獲取下資源,如果失敗就告訴前面排隊(duì)的你的下一位是我,釋放之后通知我,我要去等待了(實(shí)際上就是把前驅(qū)節(jié)點(diǎn)狀態(tài)設(shè)置成SIGNAL)。

private void setHeadAndPropagate(Node node, int propagate) {

? ? ? ? Node h = head; // Record old head for check below

? ? ? ? setHead(node);

? ? ? ? if (propagate > 0 || h == null || h.waitStatus < 0 ||

? ? ? ? ? ? (h = head) == null || h.waitStatus < 0) {

? ? ? ? ? ? Node s = node.next;

? ? ? ? ? ? if (s == null || s.isShared())

? ? ? ? ? ? ? ? doReleaseShared();

? ? ? ? }

? ? }

注意上面代碼與獨(dú)占模式的小區(qū)別,setHeadAndPropagate()執(zhí)行的時(shí)候表示當(dāng)前節(jié)點(diǎn)線程已經(jīng)獲取到了資源。此時(shí)它把自己設(shè)置為頭結(jié)點(diǎn),并需要喚醒其后繼節(jié)點(diǎn)(如果有的話),喚醒過(guò)程實(shí)際上調(diào)用了doReleaseShared方法。

C:有借有還,再借不難,釋放資源

public final boolean releaseShared(int arg) {

? ? ? ? if (tryReleaseShared(arg)) {

? ? ? ? ? ? doReleaseShared();

? ? ? ? ? ? return true;

? ? ? ? }

? ? ? ? return false;

? ? }

ok,套路與獨(dú)占模式相同,先嘗試釋放(子類自己實(shí)現(xiàn)),然后執(zhí)行釋放

private void doReleaseShared() {

? ? ? ? for (;;) {

? ? ? ? ? ? Node h = head;

? ? ? ? ? ? if (h != null && h != tail) {

? ? ? ? ? ? ? ? int ws = h.waitStatus;

? ? ? ? ? ? ? ? if (ws == Node.SIGNAL) {

? ? ? ? ? ? ? ? ? ? if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))

? ? ? ? ? ? ? ? ? ? ? ? continue;? ? ? ? ? ? // loop to recheck cases

? ? ? ? ? ? ? ? ? ? unparkSuccessor(h);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))

? ? ? ? ? ? ? ? ? ? continue;? ? ? ? ? ? ? ? // loop on failed CAS

? ? ? ? ? ? }

? ? ? ? ? ? if (h == head)? ? ? ? ? ? ? ? ? // loop if head changed

? ? ? ? ? ? ? ? break;

? ? ? ? }

? ? }

上述代碼是共享模式下釋放資源的實(shí)質(zhì)代碼。首先查看后繼線程是否需要被喚醒,如果需要那么執(zhí)行喚醒,讓它過(guò)來(lái)?yè)屨假Y源,然后會(huì)把自身狀態(tài)設(shè)置成PROPAGATE保證傳播喚醒。

非阻塞數(shù)據(jù)結(jié)構(gòu):這一層的各種數(shù)據(jù)結(jié)構(gòu)如concurrentHashMap,LinkedBlockingQueue咱們后續(xù)再寫,先趁熱打鐵把AQS的子子孫孫們扒個(gè)精光!

最頂層:

Lock:頂層接口,里面有l(wèi)ock(),unlock(),tryLock()等重要方法需要子類實(shí)現(xiàn)

Condition:頂層接口,里面有await(),signal(),signalAll()等重要方法類似于wait/notify等待通知模型。

ReadWriteLock:頂層接口,兩個(gè)方法,readLock()和writeLock()分別獲取讀鎖和寫鎖。

ReentrantLock類:

圖4:ReentrantLock類結(jié)構(gòu)

該類包含了Sync和其子類FairSync、NonfairSync,前者是公平鎖,后者是非公平鎖。默認(rèn)構(gòu)造函數(shù)是非公平鎖,可以通過(guò)boolean值構(gòu)造公平鎖。既然是一個(gè)鎖,那么最重要的兩個(gè)方法當(dāng)然是關(guān)鎖和開鎖,該類中對(duì)開鎖和關(guān)鎖分別實(shí)現(xiàn)了公平和非公平兩個(gè)方法,我們來(lái)看下具體實(shí)現(xiàn):

非公平鎖關(guān)鎖的實(shí)現(xiàn):

final void lock() {

????if (compareAndSetState(0, 1))

????????setExclusiveOwnerThread(Thread.currentThread());

????else

????????acquire(1);

}

public final void acquire(int arg) {

????if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

????????selfInterrupt();

}

final boolean nonfairTryAcquire(int acquires) {

? ? ? ? ? ? final Thread current = Thread.currentThread();

? ? ? ? ? ? int c = getState();

? ? ? ? ? ? if (c == 0) {

? ? ? ? ? ? ? ? if (compareAndSetState(0, acquires)) {

? ? ? ? ? ? ? ? ? ? setExclusiveOwnerThread(current);

? ? ? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? else if (current == getExclusiveOwnerThread()) {

? ? ? ? ? ? ? ? int nextc = c + acquires;

? ? ? ? ? ? ? ? if (nextc < 0) // overflow

? ? ? ? ? ? ? ? ? ? throw new Error("Maximum lock count exceeded");

? ? ? ? ? ? ? ? setState(nextc);

? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? }

? ? ? ? ? ? return false;

? ? ? ? }

上述代碼是非公平鎖的實(shí)現(xiàn)方式。先快速占有state資源,如果沒(méi)占有成功再調(diào)用aquire去競(jìng)爭(zhēng),競(jìng)爭(zhēng)過(guò)程又先調(diào)用了自己實(shí)現(xiàn)的tryAcquire方法:”

用當(dāng)前線程獲取AQS中的資源state,我們前文說(shuō)過(guò),state等于0表示沒(méi)有線程占用該資源,所以,這里設(shè)置獨(dú)占,而如果發(fā)現(xiàn)當(dāng)前線程已經(jīng)占用了資源(state>0并且current==owner)那么就在state基礎(chǔ)上加上獲取的次數(shù)1。

非公平鎖開鎖:

protected final boolean tryRelease(int releases) {

? ? ? ? ? ? int c = getState() - releases;

? ? ? ? ? ? if (Thread.currentThread() != getExclusiveOwnerThread())

? ? ? ? ? ? ? ? throw new IllegalMonitorStateException();

? ? ? ? ? ? boolean free = false;

? ? ? ? ? ? if (c == 0) {

? ? ? ? ? ? ? ? free = true;

? ? ? ? ? ? ? ? setExclusiveOwnerThread(null);

? ? ? ? ? ? }

? ? ? ? ? ? setState(c);

? ? ? ? ? ? return free;

? ? ? ? }

上述代碼是非公平鎖解鎖過(guò)程,之前state被重入了多少次這里就需要釋放多少次,并且把當(dāng)前占有資源的線程設(shè)置為null!直接退出就行。

公平鎖關(guān)鎖過(guò)程:

final void lock() {? ?

????acquire(1);

}

public final void acquire(int arg) {???

????if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))????

????????selfInterrupt();

}

protected final boolean tryAcquire(int acquires) {

? ? ? ? ? ? final Thread current = Thread.currentThread();

? ? ? ? ? ? int c = getState();

? ? ? ? ? ? if (c == 0) {

? ? ? ? ? ? ? ? if (!hasQueuedPredecessors() &&

? ? ? ? ? ? ? ? ? ? compareAndSetState(0, acquires)) {

? ? ? ? ? ? ? ? ? ? setExclusiveOwnerThread(current);

? ? ? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? else if (current == getExclusiveOwnerThread()) {

? ? ? ? ? ? ? ? int nextc = c + acquires;

? ? ? ? ? ? ? ? if (nextc < 0)

? ? ? ? ? ? ? ? ? ? throw new Error("Maximum lock count exceeded");

? ? ? ? ? ? ? ? setState(nextc);

? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? }

? ? ? ? ? ? return false;

? ? ? ? }

上述代碼是公平鎖實(shí)現(xiàn)過(guò)程,重點(diǎn)在?if (!hasQueuedPredecessors() &&compareAndSetState(0, acquires)) 這個(gè)hasQueuePredescessors方法!只有這個(gè)地方公平鎖與非公平鎖存在差異,該方法顧名思義,判斷當(dāng)前線程節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn),如果沒(méi)有才會(huì)獨(dú)占資源。

公平鎖的釋放過(guò)程與非公平鎖相同!

那么公平性與非公平性體現(xiàn)在哪??jī)蓚€(gè)方面:

A:非公平鎖在lock()的時(shí)候直接用當(dāng)前線程占用資源失敗之后才會(huì)調(diào)用acquire方法,這是其一

B:非公平鎖在acquire方法調(diào)用過(guò)程中調(diào)用了自己實(shí)現(xiàn)的tryAcquire方法,該方法不會(huì)等待判斷當(dāng)前線程是否有前驅(qū)節(jié)點(diǎn),而是直接用當(dāng)前線程占用state資源,這是其二

小結(jié):現(xiàn)在可以理解多線程執(zhí)行一個(gè)用lock和unlock括起來(lái)的代碼發(fā)生的事情了。

多個(gè)線程到達(dá)lock()方法

如果是公平鎖,那么多個(gè)線程同時(shí)競(jìng)爭(zhēng),競(jìng)爭(zhēng)成功的就占有state資源,競(jìng)爭(zhēng)失敗的就去clh隊(duì)列里面排隊(duì),排隊(duì)過(guò)程中自旋那么一兩次進(jìn)入await等待被前驅(qū)線程喚醒。

如果是非公平鎖,那么每個(gè)線程過(guò)來(lái)直接嘗試占有資源,如果沒(méi)有成功,那么也是不排隊(duì)直接嘗試獲取資源,如果再不成功還是要進(jìn)入clh隊(duì)列排隊(duì),自旋一兩次await等待被前驅(qū)線程喚醒。

一個(gè)線程釋放資源到達(dá)unlock方法

直接通知他的后繼節(jié)點(diǎn)過(guò)來(lái)爭(zhēng)搶資源,這個(gè)過(guò)程還存在新來(lái)的線程直接獲取state的風(fēng)險(xiǎn)(非公平)。


CountDownLatch類:

先說(shuō)下該類是干啥的,調(diào)用CountDownLatch.await()方法的線程會(huì)等待構(gòu)造方法(CountDownLatch(int i))中i個(gè)線程都執(zhí)行過(guò)countDown方法后才會(huì)繼續(xù)執(zhí)行。說(shuō)白了就是調(diào)用await方法的線程請(qǐng)客,等所有線程都到齊之后這個(gè)線程才開始做飯。

下面分析下源碼實(shí)現(xiàn):

構(gòu)造方法:構(gòu)造方法實(shí)際上把state資源設(shè)置成了多份。

public CountDownLatch(int count) {

? ? ? ? if (count < 0) throw new IllegalArgumentException("count < 0");

? ? ? ? this.sync = new Sync(count);

}

Sync(int count) {

? ? ? ? ? ? setState(count);

? }

await方法:實(shí)際上是共享模式下獲取資源,當(dāng)前線程在沒(méi)有獲取到資源的情況下會(huì)進(jìn)入到資源競(jìng)爭(zhēng)隊(duì)列,共享模式下獲取資源。假如現(xiàn)在state=10共10個(gè)資源,等待隊(duì)列里有10個(gè)線程,前8個(gè)線程獲取到了10個(gè)資源,第一個(gè)和第二個(gè)線程分別占用了兩個(gè)資源,那么當(dāng)?shù)谝粋€(gè)線程釋放了2個(gè)資源后會(huì)通知整個(gè)隊(duì)列的所有標(biāo)記為shared的10個(gè)線程來(lái)競(jìng)爭(zhēng)資源,由于競(jìng)爭(zhēng)過(guò)程是公平的,所以如果此時(shí)第9個(gè)和第10個(gè)線程分別需要1個(gè)資源,那么他們兩個(gè)都會(huì)得到滿足,加入第9個(gè)需要3個(gè)資源,那么他需要等待。這就很好的解釋了CountDownLatch的await方法,由于等待的線程獲取到共享鎖之后加入到了隊(duì)列尾部,它等待的實(shí)際上是state變?yōu)?,即所有的線程都釋放,這個(gè)時(shí)候r>0執(zhí)行return;否則state!=0證明有線程在占用共享資源,那么它可能被LockSupport》park方法await等待。

public void await() throws InterruptedException {

? ? ? ? sync.acquireSharedInterruptibly(1);

}

private void doAcquireSharedInterruptibly(int arg)

? ? ? ? throws InterruptedException {

? ? ? ? final Node node = addWaiter(Node.SHARED);

? ? ? ? boolean failed = true;

? ? ? ? try {

? ? ? ? ? ? for (;;) {

? ? ? ? ? ? ? ? final Node p = node.predecessor();

? ? ? ? ? ? ? ? if (p == head) {

? ? ? ? ? ? ? ? ? ? int r = tryAcquireShared(arg);

? ? ? ? ? ? ? ? ? ? if (r >= 0) {

? ? ? ? ? ? ? ? ? ? ? ? setHeadAndPropagate(node, r);

? ? ? ? ? ? ? ? ? ? ? ? p.next = null; // help GC

? ? ? ? ? ? ? ? ? ? ? ? failed = false;

? ? ? ? ? ? ? ? ? ? ? ? return;

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? if (shouldParkAfterFailedAcquire(p, node) &&

? ? ? ? ? ? ? ? ? ? parkAndCheckInterrupt())

? ? ? ? ? ? ? ? ? ? throw new InterruptedException();

? ? ? ? ? ? }

? ? ? ? } finally {

? ? ? ? ? ? if (failed)

? ? ? ? ? ? ? ? cancelAcquire(node);

? ? ? ? }

? ? }

countDown方法:可以看到該方法實(shí)際上是把資源state減一!!!!

public void countDown() {

? ? ? ? sync.releaseShared(1);

? ? }

public final boolean releaseShared(int arg) {

? ? ? ? if (tryReleaseShared(arg)) {

? ? ? ? ? ? doReleaseShared();

? ? ? ? ? ? return true;

? ? ? ? }

? ? ? ? return false;

? ? }

protected boolean tryReleaseShared(int releases) {

? ? ? ? ? ? // Decrement count; signal when transition to zero

? ? ? ? ? ? for (;;) {

? ? ? ? ? ? ? ? int c = getState();

? ? ? ? ? ? ? ? if (c == 0)

? ? ? ? ? ? ? ? ? ? return false;

? ? ? ? ? ? ? ? int nextc = c-1;

? ? ? ? ? ? ? ? if (compareAndSetState(c, nextc))

? ? ? ? ? ? ? ? ? ? return nextc == 0;

? ? ? ? ? ? }

? ? ? ? }

CyclicBarrier類:

先說(shuō)下這個(gè)類是干嘛的,多個(gè)線程執(zhí)行任務(wù),每個(gè)任務(wù)內(nèi)部先調(diào)用了CyclicBarrier.await()方法后就會(huì)進(jìn)入等待,直到構(gòu)造方法中i個(gè)線程都執(zhí)行了await方法才會(huì)繼續(xù)執(zhí)行任務(wù)。相當(dāng)于4個(gè)人打麻將,所有人都到齊之后4個(gè)人才開始玩起來(lái)。

這里的源碼就不做解釋了,不是基于第二層的,而是基于Condition lock等最頂層的,簡(jiǎn)單寫個(gè)用法:

public class CyclicBarrierTest2 {

????static CyclicBarrier c = new CyclicBarrier(2, new A());

????public static void main(String[] args) {

????????new Thread(new Runnable() {

????????@Override

????????public void run() {

????????try {

????????c.await();

????????} catch (Exception e) {

????????}

????????System.out.println(1);

}

????????}).start();

????try {

????c.await();

????} catch (Exception e) {

????}

????????System.out.println(2);

????}

????static class A implements Runnable {

????????@Override

????????public void run() {

????????????System.out.println(3);

????????}

????}

}

Semphore類:

Semaphore可以控制某個(gè)資源可被同時(shí)訪問(wèn)的個(gè)數(shù),acquire()獲取一個(gè)許可,如果沒(méi)有就等待,而release()釋放一個(gè)許可。比如在Windows下可以設(shè)置共享文件的最大客戶端訪問(wèn)個(gè)數(shù)。

Semphore類本質(zhì)上把state資源分成多份,通過(guò)Shared模式獲取和釋放資源,并實(shí)現(xiàn)了公平獲取釋放和非公平獲取釋放兩種操作,我們以非公平獲取釋放為例查看其源碼:

protected int tryAcquireShared(int acquires) {

? ? ? ? ? ? return nonfairTryAcquireShared(acquires);

?}

final int nonfairTryAcquireShared(int acquires) {

? ? ? ? ? ? for (;;) {

? ? ? ? ? ? ? ? int available = getState();

? ? ? ? ? ? ? ? int remaining = available - acquires;

? ? ? ? ? ? ? ? if (remaining < 0 ||

? ? ? ? ? ? ? ? ? ? compareAndSetState(available, remaining))

? ? ? ? ? ? ? ? ? ? return remaining;

? ? ? ? ? }

? ? }

上述代碼就是非公平鎖模式下獲取信號(hào)量資源,首先獲取state值(比如信號(hào)量總數(shù)為50),然后當(dāng)前線程需要的數(shù)acquires與可用的數(shù)做差看是否夠用,如果不夠用或者設(shè)置資源余量成功那么返回資源余量。注意設(shè)置資源余量方法是一定會(huì)執(zhí)行的。如果資源余量小于0又會(huì)進(jìn)入到AQS中的方法,涉及到排隊(duì),等待等等。這里不再贅述了!

if (tryAcquireShared(arg) < 0)

? ? ? ? ? ? doAcquireSharedInterruptibly(arg);

? ? }

protected final boolean tryReleaseShared(int releases) {

? ? ? ? ? ? for (;;) {

? ? ? ? ? ? ? ? int current = getState();

? ? ? ? ? ? ? ? int next = current + releases;

? ? ? ? ? ? ? ? if (next < current) // overflow

? ? ? ? ? ? ? ? ? ? throw new Error("Maximum permit count exceeded");

? ? ? ? ? ? ? ? if (compareAndSetState(current, next))

? ? ? ? ? ? ? ? ? ? return true;

? ? ? ? ? ? }

? ? ? ? }

上述是非公平模式下信號(hào)量的釋放,同樣是操作state資源,不說(shuō)了,自己看吧

公平鎖的模式就加了一個(gè)hasQueuedPredecessors判斷。。。。前文已經(jīng)解釋過(guò)了。。。。。

ConditionObject類:

Condition接口的唯一實(shí)現(xiàn)類,該類用于生產(chǎn)者消費(fèi)者模式,與Object.wait()/notify()類似但是比其更加強(qiáng)大,支持await()一段時(shí)間,還支持多個(gè)等待隊(duì)列。conditionObject最重要的兩個(gè)方法當(dāng)然是構(gòu)造方法、await()和signal()幾個(gè)方法了,下面分析其源碼:

public Condition newCondition() {

? ? ? ? return sync.newCondition();

}

上述代碼是ConditionObject的構(gòu)造方法,該Condition只能從lock中獲取,所以線程獲取Condition有兩個(gè)時(shí)機(jī),一種是調(diào)用lock()方法前,一種是調(diào)用lock()方法后。我們看下await()方法:

public final void await() throws InterruptedException {

? ? ? ? ? ? if (Thread.interrupted())

? ? ? ? ? ? ? ? throw new InterruptedException();

? ? ? ? ? ? Node node = addConditionWaiter();? //加入到當(dāng)前Condition的等待隊(duì)列

? ? ? ? ? ? int savedState = fullyRelease(node);? //完全釋放自己占有的state資源

? ? ? ? ? ? int interruptMode = 0;

? ? ? ? ? ? while (!isOnSyncQueue(node)) {? ? ? ? //如果當(dāng)前線程節(jié)點(diǎn)不在同步隊(duì)列里就一直掛起

? ? ? ? ? ? ? ? LockSupport.park(this);

? ? ? ? ? ? ? ? if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)

? ? ? ? ? ? ? ? ? ? break;

? ? ? ? ? ? }

? ? ? ? ? ? if (acquireQueued(node, savedState) && interruptMode != THROW_IE)

? ? ? ? ? ? ? ? interruptMode = REINTERRUPT;

? ? ? ? ? ? if (node.nextWaiter != null) // clean up if cancelled

? ? ? ? ? ? ? ? unlinkCancelledWaiters();

? ? ? ? ? ? if (interruptMode != 0)

? ? ? ? ? ? ? ? reportInterruptAfterWait(interruptMode);

? ? }

我們看上面代碼,關(guān)鍵部分已經(jīng)做了注釋,關(guān)鍵點(diǎn)有三,其一,Condition等待隊(duì)列是啥?其二,從哪獲取資源了需要釋放?其三,為什么判斷當(dāng)前線程是否在同步隊(duì)列里?我們先看addCondtionWaiter方法:

private Node addConditionWaiter() {

? ? ? ? ? ? Node t = lastWaiter;? ? ? ? ? ? ?

????????????//這里的隊(duì)列與同步CLH隊(duì)列不是同一個(gè)隊(duì)列,通過(guò)nextWaiter而不是next指針串起來(lái)的,每個(gè)condtion都有自己的隊(duì)列

? ? ? ? ? ? if (t != null && t.waitStatus != Node.CONDITION) { //如果最后一個(gè)節(jié)點(diǎn)被取消了就清除

? ? ? ? ? ? ? ? unlinkCancelledWaiters();

? ? ? ? ? ? ? ? t = lastWaiter;

? ? ? ? ? ? }

? ? ? ? ? ? Node node = new Node(Thread.currentThread(), Node.CONDITION); //加入對(duì)了之前新建的狀態(tài)是Condition而不是0

? ? ? ? ? ? if (t == null)

? ? ? ? ? ? ? ? firstWaiter = node;

? ? ? ? ? ? else

? ? ? ? ? ? ? ? t.nextWaiter = node;? ? ? ? ?//這里體現(xiàn)出來(lái)不是CLH同步隊(duì)列,因?yàn)檎{(diào)用的是nextWaiter=node

? ? ? ? ? ? lastWaiter = node;

? ? ? ? ? ? return node;

? ? ? ? }

上述代碼,每一個(gè)Condition都有一個(gè)自己的等待隊(duì)列,使用nextWaiter指針而不是CLH隊(duì)列使用的next指針維護(hù)著隊(duì)列。如果不調(diào)用lock()的條件下直接多線程調(diào)用Condition.await方法顯而易見(jiàn)會(huì)出現(xiàn)并發(fā)問(wèn)題,所以一般await()方法都在lock.lock()鎖內(nèi)進(jìn)行。因?yàn)樵趌ock()鎖內(nèi)進(jìn)行,所以肯定存在一個(gè)CLH隊(duì)列維護(hù)著多線程的節(jié)點(diǎn),而且當(dāng)前線程一定競(jìng)爭(zhēng)到了資源才會(huì)執(zhí)行await方法,現(xiàn)在要釋放鎖讓其他線程進(jìn)來(lái),所以調(diào)用:

final int fullyRelease(Node node) {

? ? ? ? boolean failed = true;

? ? ? ? try {

? ? ? ? ? ? int savedState = getState();? ? ? //獲取當(dāng)前線程占有的資源數(shù)目

? ? ? ? ? ? if (release(savedState)) {? ? ? ? ? ?//釋放當(dāng)前占有的資源,喚醒后繼節(jié)點(diǎn)線程,返回釋放資源的數(shù)量

? ? ? ? ? ? ? ? failed = false;

? ? ? ? ? ? ? ? return savedState;

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? throw new IllegalMonitorStateException();

? ? ? ? ? ? }

? ? ? ? } finally {

? ? ? ? ? ? if (failed)

? ? ? ? ? ? ? ? node.waitStatus = Node.CANCELLED;

? ? ? ? }

? ? }

上述代碼表示當(dāng)前線程在真正等待之前首先釋放了資源并喚醒了CLH隊(duì)列中的后繼線程。釋放了資源之后現(xiàn)在該考慮掛起了。

final boolean isOnSyncQueue(Node node) {? ? //while循環(huán)執(zhí)行該代碼

? ? ? ? if (node.waitStatus == Node.CONDITION || node.prev == null)? //如果當(dāng)前線程節(jié)點(diǎn)狀態(tài)是Condition或者不在CLH隊(duì)列中返回false

? ? ? ? ? ? return false;

? ? ? ? if (node.next != null)? ? ? ? ? ? ? ? ? ? ? //如果當(dāng)前線程節(jié)點(diǎn)有后繼節(jié)點(diǎn)證明在CLH隊(duì)列中,返回True

? ? ? ? ? ? return true;

? ? ? ? return findNodeFromTail(node);? ?//該方法同樣是判斷是否在同步隊(duì)列中

? ? }

private boolean findNodeFromTail(Node node) {

????Node t = tail;

????for (;;) {

????????if (t == node)

????????????return true;

????????if (t == null)

????????????return false;

????????????t = t.prev;

????}

}

當(dāng)前線程不在CLH隊(duì)列中就會(huì)把自己掛起,我們看下signal方法來(lái)解釋下為什么不在CLH隊(duì)列中就執(zhí)行掛起

public final void signal() {

? ? ? ? ? ? if (!isHeldExclusively())

? ? ? ? ? ? ? ? throw new IllegalMonitorStateException();

? ? ? ? ? ? Node first = firstWaiter;

? ? ? ? ? ? if (first != null)? ? ? ? ? ? ? ? //如果Condition的等待隊(duì)列里面有等待線程就執(zhí)行通知,否則啥也不做。

? ? ? ? ? ? ? ? doSignal(first);

? ? ? ? }

private void doSignal(Node first) {

? ? ? ? ? ? do {? ? ? ? ? ? //do while循環(huán),關(guān)鍵方法在transferForSignal方法

? ? ? ? ? ? ? ? if ( (firstWaiter = first.nextWaiter) == null)

? ? ? ? ? ? ? ? ? ? lastWaiter = null;

? ? ? ? ? ? ? ? first.nextWaiter = null;

? ? ? ? ? ? } while (!transferForSignal(first) &&(first = firstWaiter) != null);

? ? ? ? }

如果Condition等待隊(duì)列的頭結(jié)點(diǎn)沒(méi)有把狀態(tài)變換成SIGNAL就一直執(zhí)行do循環(huán)清空等待隊(duì)列,我們看下這個(gè)方法做了什么?

final boolean transferForSignal(Node node) {

? ? ? ? if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))? ??

? ? ? ? ? ? return false;

? ? ? ? //把當(dāng)前節(jié)點(diǎn)放入CLH隊(duì)列,這也解釋了之前為什么不在CLH隊(duì)列就一直掛起!

? ? ? ? Node p = enq(node);

? ? ? ? int ws = p.waitStatus;

? ? ? ? if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) //如果當(dāng)前節(jié)點(diǎn)被取消或者設(shè)置SIGNAL狀態(tài)失敗那么喚醒該線程。

? ? ? ? ? ? LockSupport.unpark(node.thread);

? ? ? ? return true;

? ? }

上面就是喚醒Condition線程的關(guān)鍵代碼,你可能會(huì)有疑問(wèn),從哪喚醒了等待隊(duì)列中的線程呢?哈哈,關(guān)鍵就在于enq(node)就是把當(dāng)前線程加入到了CLH隊(duì)列中并把等待節(jié)點(diǎn)的狀態(tài)設(shè)置成了SIGNAL,還記得之前CLH隊(duì)列中的設(shè)置SIGNAL嗎?(告訴前面線程如果排到號(hào)了通知我),沒(méi)錯(cuò),實(shí)際情況就是signal方法調(diào)用之后當(dāng)前線程需要重新進(jìn)入到CLH隊(duì)列競(jìng)爭(zhēng)鎖,而且是排在隊(duì)尾哦。。。

總結(jié):

最高層里面大多數(shù)的類都依賴AQS框架的state和acquire/release方法。這就是AQS框架的精妙所在,模板方法模式,一勞永逸。

本文很長(zhǎng),后續(xù)還會(huì)對(duì)阻塞隊(duì)列、并發(fā)容器、執(zhí)行器做源碼分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,310評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,566評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,055評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,303評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,799評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,683評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,899評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,135評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,528評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,844評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容