現(xiàn)已全部整理完,其他兩篇
并發(fā)整理(一)—Java并發(fā)底層原理
并發(fā)整理(三)— 并發(fā)集合類與線程池
本篇主要講鎖實(shí)現(xiàn)涉及到點(diǎn)線程
線程相關(guān)
優(yōu)先級(jí)
通過setPriority(int newPriority)
來設(shè)定,范圍為1-10,默認(rèn)是5。
更高優(yōu)先級(jí)的線程優(yōu)先運(yùn)行梯嗽,優(yōu)先的意思是只是在分配cpu時(shí)間段的時(shí)候,得到的概率高一些。
當(dāng)在某個(gè)線程創(chuàng)建一個(gè)新的線程拍柒,這個(gè)線程有與創(chuàng)建線程相同的優(yōu)先級(jí)。
線程優(yōu)先級(jí)不能作為程序正確性的依賴屈暗,因?yàn)椴糠植僮飨到y(tǒng)不一定會(huì)理會(huì)Java線程對(duì)于優(yōu)先級(jí)的設(shè)定
Daemon線程
一種支持線程拆讯,主要用作程序中后臺(tái)調(diào)度以及支持性工作。
通過setDaemon(boolean on)
必須在start之前設(shè)置养叛,不能在啟動(dòng)之后設(shè)置种呐。
JVM中只剩下Daemon線程,JVM會(huì)退出弃甥,不會(huì)支持其運(yùn)行爽室。
所以,在構(gòu)造Daemon線程時(shí)淆攻,不能依靠finally塊中的內(nèi)容來確保執(zhí)行關(guān)閉或清理資源的邏輯阔墩。
常用方法
各種狀態(tài)
Jvm中的線程有以下狀態(tài)(不是操作系統(tǒng))
NEW :初始狀態(tài),還未調(diào)用start的線程
RUNNABLE :運(yùn)行狀態(tài)瓶珊,Java線程將操作系統(tǒng)中的就緒和運(yùn)行兩種狀態(tài)籠統(tǒng)地成為"運(yùn)行中"
BLOCKED : 阻塞狀態(tài)啸箫,被鎖阻塞
-
WAITING :等待狀態(tài), 線程在這個(gè)狀態(tài)下等待其他線程執(zhí)行特定操作艰毒,通常一下操作后:
Object.wait
,
Thread.join
,
LockSupport.park
-
TIMED_WAITING :超時(shí)等待狀態(tài)筐高,不同于等待,這個(gè)狀態(tài)會(huì)在一定時(shí)間自行返回丑瞧,通常一下操作后:
Thread.sleep(long)
,
Object.wait(long)
,
Thread.join(long)
,
LockSupport.parkNanos
,
LockSupport.parkUntil
TERMINATED :終止?fàn)顟B(tài)柑土,線程已結(jié)束
鎖相關(guān)
關(guān)于CAS
CAS是一種無鎖并發(fā)技術(shù),是并發(fā)中很重要的技術(shù)绊汹,用來原子的更新數(shù)據(jù)
簡(jiǎn)單說就是CAS(Compare and Swap)比較并替換稽屏,先獲取舊值,然后修改西乖,再替換狐榔,在替換的過程中如果發(fā)現(xiàn)舊值和原來不一樣坛增,則說明其他線程也在修改,自己已經(jīng)臟讀薄腻,所以本次失敗
隱式鎖
Java中的指的就是synchronized收捣。是一種可重入/非公平/悲觀/獨(dú)占鎖。
它是由JVM實(shí)現(xiàn)的庵楷,基于面向?qū)ο笾?strong>Monitor Object設(shè)計(jì)模式來實(shí)現(xiàn)的
- 使用synchronized獲得對(duì)象鎖罢艾,來保證互斥
- notify/notifyAll/wait 方法來協(xié)同不同線程之間的工作
- 將條件值儲(chǔ)存在對(duì)象頭中
關(guān)于這個(gè)模式可以看
對(duì)象頭
Java對(duì)象頭中的MarkWord儲(chǔ)存鎖標(biāo)記位

鎖升級(jí)
很多人把synchronized叫重量鎖,但是jdk6之后進(jìn)行優(yōu)化了其性能尽纽,通過不同狀態(tài)來采取策略咐蚯。可以看到總共有四種狀態(tài)由低到高:無鎖狀態(tài)弄贿、偏向鎖狀態(tài)春锋、輕量級(jí)鎖狀態(tài)、重量級(jí)鎖狀態(tài)差凹。并且采取只能升級(jí)不降級(jí)的策略期奔。
鎖升級(jí)如下:
-
當(dāng)一個(gè)線程訪問同步塊,先看記錄直奋,如果沒有儲(chǔ)存線程ID則用CAS替換MarkWord并且置偏向鎖能庆。
之后每次同一個(gè)線程進(jìn)入只要檢查MarkWord狀態(tài)就行,沒有其他額外開銷脚线。
如果有其他線程進(jìn)入搁胆,檢測(cè)MarkWord是否有自己線程ID,發(fā)現(xiàn)沒有采用CAS替換并且失敗了邮绿,檢查是否偏向線程還活著渠旁,不活著就置無鎖,活動(dòng)則遍歷鎖記錄船逮,然后決定是否升級(jí)顾腊。
-
到輕量鎖后在鎖記錄添加,并且CAS替換指向鎖指針挖胃,如果成功則沒有競(jìng)爭(zhēng)杂靶,并獲得鎖。
失敗則說明存在競(jìng)爭(zhēng)酱鸭,所以升級(jí)吗垮。
-
重量級(jí)鎖就會(huì)用到Monitor
waitnotify運(yùn)行過程
顯式鎖
雖然synchronized可以獲取鎖,但是其將鎖固化了凹髓,有時(shí)不夠靈活烁登,所以JDK5后新增了Lock接口并且相關(guān)實(shí)現(xiàn)類,來方便我們實(shí)現(xiàn)更高的需求蔚舀。
Lock接口
所有的鎖實(shí)現(xiàn)都要實(shí)現(xiàn)這個(gè)接口符合規(guī)范
public interface Lock {
//只有當(dāng)鎖獲得后才會(huì)從該方法返回饵沧,否則阻塞
void lock();
//提供synchronized做不到的可中斷獲取鎖
void lockInterruptibly() throws InterruptedException;
//嘗試獲取鎖锨络,不會(huì)阻塞
boolean tryLock();
//提供synchronized做不到的超時(shí)獲取鎖,可被中斷
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
//獲取鎖的協(xié)同條件變量狼牺,如obj.wait一樣使用
Condition newCondition();
}
AQS
AQS(AbstractQueuedSynchronizer)可以稱作同步器羡儿,每個(gè)鎖都會(huì)有一個(gè)其實(shí)現(xiàn)并作為內(nèi)部類,用來管理線程來獲取鎖锁右。
結(jié)構(gòu)
public abstract class AbstractQueuedSynchronizer{
private volatile int state;//控制同步的狀態(tài)量
//設(shè)計(jì)者希望其能夠簡(jiǎn)化鎖的實(shí)現(xiàn)失受,所以封裝了大量的操作讶泰。主要提供了共享與非共享的同步狀態(tài)管理咏瑟。
//采用模板方法的設(shè)計(jì)模式,其模板方法主要如下:
//獨(dú)占式獲取同步狀態(tài)的幾個(gè)方法
public final void acquire(int arg) {...}
public final void acquireInterruptibly(int arg){...}//可中斷
public final boolean tryAcquireNanos(int arg, long nanosTimeout){...}//超時(shí)獲取
public final boolean release(int arg) {...}
//共享式獲取同步狀態(tài)的幾個(gè)方法
public final void acquireShared(int arg) {...}
public final void acquireSharedInterruptibly(int arg){...}
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout){...}
public final boolean releaseShared(int arg) {...}
//還有一些工具方法痪署,返回等待線程情況
public final Collection<Thread> getQueuedThreads() {...}
//模板中調(diào)用的核心方法希望我們?cè)谧宇愔袑?shí)現(xiàn)
//重寫的時(shí)候修改Status時(shí)要用AQS給我們提供的compareAndSetState方法來CAS設(shè)置
protected boolean tryAcquire(int arg) {}
protected boolean tryRelease(int arg) {}
protected int tryAcquireShared(int arg) {}//共享式獲取返回值>0代表成功
protected boolean tryReleaseShared(int arg) {}
protected boolean isHeldExclusively() {}//表示是否被當(dāng)前線程獨(dú)占
//以下部分是AQS內(nèi)部維護(hù)的CLH同步隊(duì)列
private transient volatile Node head;
private transient volatile Node tail;
static final class Node {
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
...
}
//鎖內(nèi)的條件變量也封裝在AQS中
public class ConditionObject implements Condition{...}
}
同步隊(duì)列管理
可以看到AQS就是封裝了這部分码泞,這也是鎖最重要的部分,因?yàn)槠浞庋b了獨(dú)占式和共享式的同步方式狼犯,兩者略有不同余寥,現(xiàn)在看一下其實(shí)現(xiàn)。
鎖獲取
acquire
悯森、acquireShared
就是這兩個(gè)方法宋舷,源碼很少可以自己看
- 不論獨(dú)占式還是共享式獲取鎖的時(shí)候,首先調(diào)用
tryAcquire
或tryAcquireShared
來嘗試CAS改變Status - 如果失敗則通過
addWaiter
來創(chuàng)建一個(gè)Node瓢姻,并加入隊(duì)列(也是CAS)祝蝠,如果加入失敗則一直循環(huán)添加知道成功 - 唯一區(qū)別在于,共享式創(chuàng)建的Node的nextWaiter是一個(gè)SHARED常量來標(biāo)志幻碱,而獨(dú)占式標(biāo)志是一個(gè)NULL節(jié)點(diǎn)
隊(duì)列中等待
- 進(jìn)入隊(duì)列后就開始進(jìn)入一個(gè)死循環(huán)自旋绎狭,不過不是無腦循環(huán),通過
LockSupport.park()
來暫停線程褥傍,直到被喚醒再轉(zhuǎn)儡嘶,每次轉(zhuǎn)每次判斷 - 被喚醒并且判斷到一個(gè)節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)是一個(gè)頭結(jié)點(diǎn)了,然后通過
tryAcquire
或tryAcquireShared
再來CAS一次 - 獨(dú)占式如果改變Status成功則就直接設(shè)置為頭結(jié)點(diǎn)就返回了恍风,但是共享式會(huì)進(jìn)行判斷蹦狂,如果是SHARED并且Status>=0則會(huì)設(shè)置頭結(jié)點(diǎn)并且直接將下一個(gè)節(jié)點(diǎn)也unpark喚醒以便傳遞下去
鎖釋放
- 通過調(diào)用
release
或releaseShared
來CAS改變Status,并且LockSupport.unpark()
來喚醒下一個(gè)節(jié)點(diǎn)朋贬,然后返回 - 這里共享式不同于獨(dú)占式區(qū)別在于共享式由于一直傳遞可能存在CAS失敗凯楔,所以采用一直循環(huán)來保證Status更新成功安全釋放
支持超時(shí)獲取同步狀態(tài)
- AQS讓線程不僅可以支持中斷獲取,還支持超時(shí)且可中斷獲取兄世,并且做了優(yōu)化
- 優(yōu)化就是不是無腦自旋判斷時(shí)間啼辣,而是通過spinForTimeoutThreshold(1000ns)來作為分界線,大于分界值就采用定時(shí)
park
到分界值御滩,小于就進(jìn)入快速自旋做到精確時(shí)間
AbstractQueuedSynchronizer的介紹和原理分析
可重入鎖
可重入鎖ReentrantLock就是典型的Lock接口實(shí)現(xiàn)鸥拧,繼承了AQS并且實(shí)現(xiàn)tryAcquire
還添加了nofairTryAcquire
來支持公平與非公平鎖的選擇党远。代碼非常少,可以自己看富弦。
主要注意的:
- 之所以叫可重入鎖沟娱,就是因?yàn)?code>tryAcquire與
nofairTryAcquire
都對(duì)是否是當(dāng)前線程進(jìn)行了判斷,如果是則不會(huì)阻塞腕柜,也不會(huì)加入節(jié)點(diǎn)济似,只是CAS改變Status -
tryAcquire
與nofairTryAcquire
兩個(gè)方法唯一的區(qū)別就是在申請(qǐng)的時(shí)候公平鎖增加了一個(gè)判斷當(dāng)前節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn)來保證FIFO隊(duì)列實(shí)現(xiàn)公平,而nofair則沒有這個(gè)判斷盏缤,所以支持闖入機(jī)制砰蠢,可能會(huì)被一個(gè)剛來的線程搶走,所以是不公平的
Condition
可以看到ConditionObjective是AQS的內(nèi)部類
與Obj監(jiān)視器方法類似唉铜,通過Lock.newCondition()
方法能夠創(chuàng)建與Lock綁定的Condition實(shí)例台舱。
await()
對(duì)應(yīng)于Object.wait()
signal()
對(duì)應(yīng)于Object.notify()
signalAll()
對(duì)應(yīng)于Object.notifyAll()
。
當(dāng)然這幾個(gè)方法與synchronized
一樣潭流,需要先Lock.lock()
獲取鎖竞惋。
注意的是:
- 在Obj的監(jiān)視器模型上,一個(gè)對(duì)象擁有一個(gè)同步隊(duì)列和一個(gè)等待隊(duì)列灰嫉。但是AQS中有一個(gè)上面說的同步隊(duì)列拆宛,然后每一個(gè)ConditionObjective都對(duì)應(yīng)一個(gè)等待隊(duì)列
- 當(dāng)調(diào)用方法的線程獲取了鎖,也就是成為了同步隊(duì)列的頭結(jié)點(diǎn)讼撒,
await
會(huì)將新生成一個(gè)節(jié)點(diǎn)加入等待隊(duì)列浑厚,然后釋放鎖,喚醒同步隊(duì)列中的下一個(gè)節(jié)點(diǎn) - 節(jié)點(diǎn)加入等待隊(duì)列后就會(huì)進(jìn)行循環(huán)判斷椿肩,如果沒有被
signal
喚醒瞻颂,也就是沒有加入到同步隊(duì)列則會(huì)park
自己 -
signal
在獲取鎖的情況下,將節(jié)點(diǎn)重新CAS移到同步隊(duì)列郑象,并且將節(jié)點(diǎn)線程unpark
贡这,然后節(jié)點(diǎn)線程就會(huì)從await
返回
ReentrantReadWriteLock
同樣實(shí)現(xiàn)Lock接口,包含AQS厂榛,只是比ReentrantLock再復(fù)雜一些盖矫,是一個(gè)可重入共享鎖,能夠做到比排它鎖更好的并發(fā)性和吞吐量
- 其包含一個(gè)ReadLock和一個(gè)WriteLock击奶,允許讀讀辈双,不允許讀寫,寫寫并發(fā)柜砾,并且支持鎖降級(jí)
- 其將AQS中的Status拆分湃望,高16位給讀鎖,低16位給寫鎖,這樣來滿足兩個(gè)鎖記錄
- 其AQS同樣分別寫了公平與非公平兩種支持
工具類
CountDownLatch
可以實(shí)現(xiàn)類似計(jì)數(shù)器的功能证芭,做到允許一個(gè)或多個(gè)線程等待其他線程完成操作
- 構(gòu)造方法傳入計(jì)數(shù)瞳浦,計(jì)數(shù)器等于0就不會(huì)阻塞,并且不能重新初始化與修改計(jì)數(shù)
- 通過簡(jiǎn)單2個(gè)方法
await
,countDown
來控制废士,一個(gè)線程調(diào)用countDown
方法happen-before另外一個(gè)線程調(diào)用await
CyclicBarrier
是一種可循環(huán)使用的屏障叫潦,做到一組線程到達(dá)一個(gè)屏障是被阻塞,知道最后一個(gè)線程到達(dá)屏障官硝,才會(huì)開門矗蕊。常用于Fork/Join操作。
其與CountDownLatch最大區(qū)別在于氢架,如果計(jì)算發(fā)生錯(cuò)誤傻咖,可以重置計(jì)數(shù)器,讓線程重新執(zhí)行达箍,所以用途更廣泛
Semaphore
與操作系統(tǒng)中的信號(hào)量類似没龙,通過協(xié)調(diào)各個(gè)線程,來保證合理的訪問限定的資源數(shù)
其內(nèi)部依然實(shí)現(xiàn)AQS來管理各個(gè)線程的同步狀態(tài)
簡(jiǎn)單通過semp.acquire()
,semp.release()
來進(jìn)行許可請(qǐng)求和釋放