J.U.C之AQS:大話AQS詳解和使用

J.U.C之AQS:AQS詳解和使用

AQS是什么

AQS是AbstractQueuedSynchronizer的縮寫,翻譯過來就是"同步器",AbstractQueuedSynchronizer是一個抽象類,它實現(xiàn)了Java函數(shù)中鎖同步(synchronized)液样,鎖等待(wait,notify)功能寓搬。

Java并包里大部分并發(fā)工具類都將其作為核心基礎構件寥枝,比如可重入鎖ReentrantLock, 信號量Semaphore基于各自的特點來使用AQS提供的基礎能力方法實現(xiàn)多線程交互碍拆。

AQS核心功能

鎖同步(synchronized)

鎖等待(wait,notify)

AQS 中概念

同步狀態(tài)

AQS實現(xiàn)了鎖蓉驹,必然需要一個競爭對象列另。AQS存在從一個int類型的成員變量state,我們把它稱為同步狀態(tài),基于開閉原則往弓,內部提供了很多模板方法【參考AQS核心方法】給子類去定制如何獲取釋放同步狀態(tài)疏唾。

AQS按照獲取釋放同步狀態(tài)的方式分為"獨占式同步","共享式同步"。

獨占式同步

從概念上來說獨占式對應只存在一個資源函似,且只能被一個線程或者說競爭者占用.

共享式同步

從概念上來共享式對應存在多個資源的是有多個線程或者競爭者能夠獲取占用槐脏。他們對應的場景不同因而流程上會有差異

同步隊列

AQS 實現(xiàn)了鎖那么總需要一個隊列將無法獲取鎖的線程保存起來,方便在鎖釋放時通知隊列中線程去重新競爭鎖缴淋。

同步隊列又被稱為CLH同步隊列准给,CLH隊列是通過鏈式方式實現(xiàn)FIFO雙向隊列。當線程獲取同步狀態(tài)失敗時重抖,AQS則會將當前線程構造成一個節(jié)點(Node)并將其加入到CLH同步隊列露氮,同時會阻塞當前線程,當同步狀態(tài)被釋放時钟沛,會把首節(jié)點后第一個節(jié)點的線程從阻塞狀態(tài)下喚醒畔规,喚醒的線程會嘗試競爭同步狀態(tài),如果獲取同步狀態(tài)成功恨统,則從同步隊列中出隊,如果獲取同步狀態(tài)失敗則繼續(xù)阻塞叁扫。

同步隊列.png
栗子
public class LockDemo {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                lock.lock();
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            });
            thread.start();
        }
    }
}

實例代碼中開啟了5個線程三妈,先獲取鎖之后再睡眠10S中。通過debug莫绣,當Thread-4(在本例中最后一個線程)獲取鎖失敗后進入同步時畴蒲,AQS時現(xiàn)在的同步隊列如圖所示

同步栗子.png

條件隊列

Java 傳統(tǒng)的監(jiān)視器有如下函數(shù) wait、notify对室、notifyAll模燥。

它們可以實現(xiàn)當一個線程獲取鎖時,它可以主動放棄(wait)鎖進入阻塞狀態(tài)掩宜,同時被添加進入一個條件隊列中蔫骂。只有其他線程通知喚醒(notify/notifyAll)時才從條件隊列中出隊,并嘗試獲取鎖牺汤,并在獲取鎖成功后繼續(xù)執(zhí)行之前的未完成代碼邏輯辽旋。

AQS內部存在一個內部類實現(xiàn)了Condition接口, 在AQS內部維護著一條鏈表實現(xiàn)單向條件隊列。使用AQS獲取內部實現(xiàn)Condition接口對象檐迟,調用await()补胚,signal(),signalAll()函數(shù)實現(xiàn)Java中wait、notify锅减、notifyAll同樣功能糖儡。

  • 當獲取同步狀態(tài)的線程調用condition.await(),當前線程會阻塞,并進入一個等待隊列,釋放同步狀態(tài).

  • 當其他線程調用了condition.signal()方法,會從等待隊列firstWaiter開始選擇第一個等待狀態(tài)不是取消的節(jié)點.添加到同步隊列尾部.

  • 當其他線程調用了condition.signalAll()方法,會從等待隊列firstWaiter開始選擇所有等待狀態(tài)不是取消的節(jié)點.添加到同步隊列尾部.

  • 這里取消節(jié)點表示當前節(jié)點的線程不在參與排隊獲取鎖。

條件隊列.png
栗子
public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        Thread thread = new Thread(() -> {
            lock.lock();
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        });
        thread.start();
    }
}

新建了10個線程怔匣,每個線程先獲取鎖,然后調用condition.await方法釋放鎖將當前線程加入到等待隊列中桦沉,通過debug控制當走到第10個線程的時候查看firstWaiter即等待隊列中的頭結點每瞒,debug模式下情景圖如下:

條件隊列栗子.png

AQS 實現(xiàn)原理

AQS核心是一個同步狀態(tài),兩個隊列纯露。它們實現(xiàn)了Java函數(shù)中鎖同步(synchronized)剿骨,鎖等待(wait,notify),并在其基礎上實現(xiàn)了獨占式同步埠褪,共享式同步2中方式鎖的實現(xiàn)浓利。

  • 無論獨占式還時共享式獲取同步狀態(tài)成功則直接返回,

  • 當線程獲取同步狀態(tài)失敗時钞速,AQS則會將當前線程構造成一個節(jié)點(Node)并將其加入到CLH同步隊列贷掖,同時會阻塞當前線程,

  • 當同步狀態(tài)被釋放時渴语,會把首節(jié)點后第一個節(jié)點的線程從阻塞狀態(tài)下喚醒苹威,喚醒的線程會嘗試競爭同步狀態(tài),如果獲取同步狀態(tài)成功驾凶,則從同步隊列中出隊,如果獲取同步狀態(tài)失敗則繼續(xù)阻塞牙甫。

同步隊列.png
  • 當獲取同步狀態(tài)的線程調用condition.await(),當前線程會阻塞,并進入一個等待隊列,釋放同步狀態(tài).掷酗。

  • 當其他線程調用了condition.signal()方法,會從等待隊列firstWaiter開始選擇第一個等待狀態(tài)不是取消的節(jié)點.添加到同步隊列尾部.

  • 當其他線程調用了condition.signalAll()方法,會從等待隊列firstWaiter開始選擇所有等待狀態(tài)不是取消的節(jié)點.添加到同步隊列尾部.

  • 這里取消節(jié)點表示當前節(jié)點的線程不在參與排隊獲取鎖。

條件隊列.png

AQS核心方法

獨占式同步

acquire

獲取同步狀態(tài)窟哺。如果當前線程獲取同步狀態(tài)成功則直接返回泻轰,如果獲取失敗插入同步隊列尾部,同時線程阻塞且轨。當調用release釋放同步狀態(tài)時糕殉,會從同步隊列head頭部后第一個節(jié)點中線程從阻塞中釋放并在自旋中重新競爭同步狀態(tài),如果獲取成功則從同步隊列出隊殖告,并返回阿蝶,如果獲取失敗則繼續(xù)阻塞,等待下次喚醒黄绩。

acquireInterruptibly

獨占式獲取同步狀態(tài),與acquire方法相同羡洁。但在如果當前線程被中斷,則該方法會拋出InterruptedException異常并返回爽丹;

tryAcquireNanos

獨占式獲取同步狀態(tài),與acquireInterruptibly方法相同筑煮。但在acquireInterruptibly基礎上增加了超時等待功能,在超時時間內沒有獲得同步狀態(tài)返回false

release

釋放獨占式同步狀態(tài)粤蝎,喚醒同步隊列中首節(jié)點之后的第一個等待節(jié)點的線程的阻塞真仲。

共享式同步

acquireShared

獲取同步狀態(tài),如果當前線程獲取同步狀態(tài)成功則直接返回。如果獲取失敗插入同步隊列尾部初澎,同時線程阻塞秸应。當調用releaseShared釋放同步狀態(tài)時,會找到從head頭部節(jié)點后置節(jié)點中的線程碑宴,并將該線程從阻塞中釋放软啼。被釋放的線程會在自旋中重新競爭同步狀態(tài)。如果獲取成功則出隊延柠,同時會釋放后置節(jié)點中的線程從阻塞中喚醒競爭同步狀態(tài)祸挪。

acquireSharedInterruptibly

在acquireShared方法基礎上增加了能響應中斷的功能;

tryAcquireSharedNanos

在acquireSharedInterruptibly基礎上增加了超時等待的功能贞间;

releaseShared

釋放共享式同步狀態(tài)贿条,釋放共享式同步狀態(tài)會喚醒同步隊列中首節(jié)點之后的第一個等待節(jié)點的線程的阻塞。

同步狀態(tài)

getState()

返回同步狀態(tài)的當前值

setState(int newState)

設置當前同步狀態(tài)

compareAndSetState(int expect, int update)

使用CAS設置當前狀態(tài)增热,該方法能夠保證狀態(tài)設置的原子性

同步隊列

hasQueuedThreads()

查詢是否有任何線程正在等待獲取整以。【在同步隊列中是否存在等待線程】

int getQueueLength()

返回等待獲取的線程數(shù)的估計值.在同步隊列中是否存在等待線程數(shù)量】

getQueuedThreads()

返回包含可能等待獲取的線程的集合钓葫。因為實際的線程集可能在構造此結果時動態(tài)地改變悄蕾,所以返回的集合僅是盡力而為的估計值【返回同步隊列中線程集合】

AQS模板方法

我們可以編寫自己類繼承AQS選擇重寫獨占式或共享式模板方法,從而定義如何獲取同步狀態(tài)和釋放同步狀態(tài)的邏輯。

獨占式

tryAcquire

嘗試獨占式獲取同步狀態(tài),返回值為true則獲得同步狀態(tài)成功,否則獲取失敗帆调。

調用場景:

  • 1 在acquire開始判斷獲取同步狀態(tài)時調用

  • 2 在acquire自旋循環(huán)中每次都會獲取同步狀態(tài)時調用(該線程從阻塞中釋放,會在自旋中重新競爭同步狀態(tài))

tryRelease

嘗試獨占式釋放同步狀態(tài)奠骄,返回值為true則表示獲取成功,否則獲取失敗番刊。

調用場景:

  • 1 該方法在釋放獨占式同步狀態(tài)【release方法】時

共享式

tryAcquireShared

嘗試共享式獲取同步狀態(tài),當返回值為大于等于0的時獲得同步狀態(tài)成功含鳞,否則獲取失敗。

調用場景:

  • 1 在acquireShared開始判斷獲取同步狀態(tài)時調用芹务。

  • 2 在acquireShared自旋循環(huán)中每次都會獲取同步狀態(tài)時調用

(該線程從阻塞中釋放,會在自旋中重新競爭同步狀態(tài))

tryReleaseShared

嘗試共享式釋放同步狀態(tài)蝉绷,返回值為true則表示獲取成功,否則獲取失敗枣抱。

調用場景:

  • 該方法在釋放共享式同步狀態(tài)【releaseShared方法】時會調用熔吗。

獨占式 VS 共享式

從概念上來說獨占式對應只存在一個資源,且只能被一個線程或者說競爭者占用佳晶,而共享式對應存在多個資源的是有多個線程或者競爭者能夠獲取占用桅狠。他們對應的場景不同因而流程上會有差異。

流程區(qū)別

在流程上來看只有加粗的部分是共享式所獨有的:

嘗試獲取同步失敗 --> 進入等待隊列排隊 --> 阻塞當前線程 --> 當?shù)却犃信诺阶约罕粏拘?--> 嘗試獲取鎖(可能被其他線程插隊而導致獲取鎖失敗轿秧,失敗在次阻塞中跌,等待下次排到自己)--> 嘗試獲取鎖成功,通知等待隊列前面共享節(jié)點線程從阻塞中喚醒 --> 執(zhí)行自己的業(yè)務邏輯 --> 嘗試釋放鎖--> 成功通知等待隊列前面共享節(jié)點線程從阻塞中喚醒菇篡。

獨占式

對于獨占式漩符,當節(jié)點中線程從阻塞中釋放,獲取同步狀態(tài)成功后驱还,就開始執(zhí)行嗜暴,直到完成,釋放同步狀態(tài)才會通知等待隊列節(jié)點線程從阻塞中喚醒铝侵,由于獨占式通常只有一個資源灼伤。因而就沒有必要在獲取鎖成功后通知同步隊列線程去嘗試獲取同步狀態(tài),因為自己還沒做完呢咪鲜,也就符合同時只能一人執(zhí)行特性。只有自己執(zhí)行完后才通知同步隊列線程獲取同步狀態(tài)撞鹉。

共享式

對于共享式疟丙,當節(jié)點中線程從阻塞中釋放,獲取同步成功鸟雏,在開始執(zhí)行任務前享郊,由于存在多個資源。因而更加積極孝鹊,他通知后置節(jié)點線程從阻塞中喚醒炊琉,如果后置節(jié)點同樣獲取同步狀態(tài)成功,則相當于同時有多個人在執(zhí)行,也可以說釋放鎖可以多個線程進入苔咪。且這種方式具有傳播性锰悼。直到某個節(jié)點獲取同步狀態(tài)失敗才停止這種傳播。

相同點

無論是獨占和共享都提供了模板方法去定制能否獲取同步和能否釋放同步团赏。

小結

能否獲取同步決定了同時又多少人同時執(zhí)行箕般。和資源相關和獨占式還是共享式無關。對于當個資源我們通常使用獨占式舔清,對于多個資源我們通常使用共享式丝里。又時候你甚至將獲取同步寫成默認返回true,表示無鎖。就好像是一個閘門絕對能放如多少水進入水庫体谒。是不是獨占也能實現(xiàn)共享鎖呢杯聚?答案是可以的。只不過他并沒有共享式那么積極抒痒!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末幌绍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子评汰,更是在濱河造成了極大的恐慌纷捞,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,948評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件被去,死亡現(xiàn)場離奇詭異主儡,居然都是意外死亡,警方通過查閱死者的電腦和手機惨缆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評論 3 385
  • 文/潘曉璐 我一進店門糜值,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人坯墨,你說我怎么就攤上這事寂汇。” “怎么了捣染?”我有些...
    開封第一講書人閱讀 157,490評論 0 348
  • 文/不壞的土叔 我叫張陵骄瓣,是天一觀的道長。 經常有香客問我耍攘,道長榕栏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,521評論 1 284
  • 正文 為了忘掉前任蕾各,我火速辦了婚禮扒磁,結果婚禮上,老公的妹妹穿的比我還像新娘式曲。我一直安慰自己妨托,他們只是感情好,可當我...
    茶點故事閱讀 65,627評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著兰伤,像睡著了一般内颗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上医清,一...
    開封第一講書人閱讀 49,842評論 1 290
  • 那天起暮,我揣著相機與錄音,去河邊找鬼会烙。 笑死负懦,一個胖子當著我的面吹牛,可吹牛的內容都是我干的柏腻。 我是一名探鬼主播纸厉,決...
    沈念sama閱讀 38,997評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼五嫂!你這毒婦竟也來了颗品?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,741評論 0 268
  • 序言:老撾萬榮一對情侶失蹤沃缘,失蹤者是張志新(化名)和其女友劉穎躯枢,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體槐臀,經...
    沈念sama閱讀 44,203評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡锄蹂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,534評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了水慨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片得糜。...
    茶點故事閱讀 38,673評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖晰洒,靈堂內的尸體忽然破棺而出朝抖,到底是詐尸還是另有隱情,我是刑警寧澤谍珊,帶...
    沈念sama閱讀 34,339評論 4 330
  • 正文 年R本政府宣布治宣,位于F島的核電站,受9級特大地震影響砌滞,放射性物質發(fā)生泄漏炼七。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,955評論 3 313
  • 文/蒙蒙 一布持、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧陕悬,春花似錦题暖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唯绍。三九已至,卻和暖如春枝誊,著一層夾襖步出監(jiān)牢的瞬間况芒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評論 1 266
  • 我被黑心中介騙來泰國打工叶撒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绝骚,地道東北人。 一個月前我還...
    沈念sama閱讀 46,394評論 2 360
  • 正文 我出身青樓祠够,卻偏偏與公主長得像压汪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子古瓤,可洞房花燭夜當晚...
    茶點故事閱讀 43,562評論 2 349

推薦閱讀更多精彩內容