并發(fā)整理(二)— Java線程與鎖

現(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)閉或清理資源的邏輯阔墩。

常用方法

Thread常用方法

各種狀態(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é)束

這個(gè)圖還不錯(cuò)
這個(gè)圖還不錯(cuò)

鎖相關(guān)

關(guān)于CAS

CAS是一種無鎖并發(fā)技術(shù),是并發(fā)中很重要的技術(shù)绊汹,用來原子的更新數(shù)據(jù)

簡(jiǎn)單說就是CAS(Compare and Swap)比較并替換稽屏,先獲取舊值,然后修改西乖,再替換狐榔,在替換的過程中如果發(fā)現(xiàn)舊值和原來不一樣坛增,則說明其他線程也在修改,自己已經(jīng)臟讀薄腻,所以本次失敗

JavaCAS深度分析

隱式鎖

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è)模式可以看

從C++來理解Java中的Java 同步機(jī)制

對(duì)象頭

Java對(duì)象頭中的MarkWord儲(chǔ)存鎖標(biāo)記位

MarkWord儲(chǔ)存狀態(tài)
MarkWord儲(chǔ)存狀態(tài)

鎖升級(jí)

很多人把synchronized叫重量鎖,但是jdk6之后進(jìn)行優(yōu)化了其性能尽纽,通過不同狀態(tài)來采取策略咐蚯。可以看到總共有四種狀態(tài)由低到高:無鎖狀態(tài)弄贿、偏向鎖狀態(tài)春锋、輕量級(jí)鎖狀態(tài)、重量級(jí)鎖狀態(tài)差凹。并且采取只能升級(jí)不降級(jí)的策略期奔。

鎖升級(jí)如下:

  1. 當(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í)顾腊。

  2. 到輕量鎖后在鎖記錄添加,并且CAS替換指向鎖指針挖胃,如果成功則沒有競(jìng)爭(zhēng)杂靶,并獲得鎖。

    失敗則說明存在競(jìng)爭(zhēng)酱鸭,所以升級(jí)吗垮。

  3. 重量級(jí)鎖就會(huì)用到Monitor

    waitnotify運(yùn)行過程
    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)用tryAcquiretryAcquireShared來嘗試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)了,然后通過tryAcquiretryAcquireShared再來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)用releasereleaseShared來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
  • tryAcquirenofairTryAcquire兩個(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)求和釋放

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缎玫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子解滓,更是在濱河造成了極大的恐慌赃磨,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洼裤,死亡現(xiàn)場(chǎng)離奇詭異邻辉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)腮鞍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門值骇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人移国,你說我怎么就攤上這事吱瘩。” “怎么了迹缀?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵使碾,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我祝懂,道長(zhǎng)票摇,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任砚蓬,我火速辦了婚禮矢门,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己祟剔,他們只是感情好傅事,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著峡扩,像睡著了一般蹭越。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上教届,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天响鹃,我揣著相機(jī)與錄音,去河邊找鬼案训。 笑死买置,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的强霎。 我是一名探鬼主播忿项,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼城舞!你這毒婦竟也來了轩触?” 一聲冷哼從身側(cè)響起项阴,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤肮柜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后酸舍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拉馋,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡榨为,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了煌茴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片随闺。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蔓腐,靈堂內(nèi)的尸體忽然破棺而出矩乐,到底是詐尸還是另有隱情,我是刑警寧澤合住,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布绰精,位于F島的核電站,受9級(jí)特大地震影響透葛,放射性物質(zhì)發(fā)生泄漏笨使。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一僚害、第九天 我趴在偏房一處隱蔽的房頂上張望硫椰。 院中可真熱鬧繁调,春花似錦、人聲如沸靶草。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奕翔。三九已至裕寨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間派继,已是汗流浹背宾袜。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驾窟,地道東北人庆猫。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像绅络,于是被迫代替她去往敵國(guó)和親月培。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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