API翻譯之AbstractQueuedSynchronizer

AbstractQueuedSynchronizer

??提供一個(gè)框架皆串,用于實(shí)現(xiàn)依賴先進(jìn)先出(FIFO)等待隊(duì)列的阻塞鎖和相關(guān)同步器(信號(hào)量惰聂、事件等)异剥。該類被設(shè)計(jì)為大多數(shù)依賴于單個(gè)原子int值來表示狀態(tài)的同步器的有用基礎(chǔ)桅狠。子類必須定義改變?cè)摖顟B(tài)的受保護(hù)方法潮瓶,以及這些方法定義了該狀態(tài)在獲取或釋放該對(duì)象方面的含義蚕泽∩卫妫考慮到這些,該類中的其他方法執(zhí)行所有隊(duì)列和阻塞機(jī)制须妻。子類可以維護(hù)其他狀態(tài)字段仔蝌,但是只有使用方法getStatesetStatecompareAndSetState對(duì)原子更新的int值進(jìn)行同步跟蹤荒吏。

??子類應(yīng)該被定義為非公共的內(nèi)部幫助類敛惊,用于實(shí)現(xiàn)其封閉類的同步屬性。類AbstractQueuedSynchronizer不實(shí)現(xiàn)任何同步接口绰更。相反瞧挤,它定義了諸如acquireInterruptibly這樣的方法锡宋,具體鎖和相關(guān)同步器可以根據(jù)需要調(diào)用這些方法來實(shí)現(xiàn)它們的公共方法。

??這個(gè)類支持默認(rèn)的\color{red}{獨(dú)占的}模式和\color{red}{共享的}模式特恬。在獨(dú)占模式下獲取時(shí)执俩,其他線程試圖獲取的操作無法成功。多個(gè)線程獲取的共享模式可能(但不需要)成功癌刽。這個(gè)類不“明白”除了機(jī)械意義上的差異之外役首,這些差異還包括:當(dāng)共享模式獲取成功時(shí),下一個(gè)等待的線程(如果存在的話)也必須確定它是否能夠獲取妒穴。在不同模式下等待的線程共享同一個(gè)FIFO隊(duì)列宋税。通常,實(shí)現(xiàn)子類只支持其中一種模式讼油,但這兩種模式都可以發(fā)揮作用杰赛,例如在ReadWriteLock中。只支持獨(dú)占模式或僅支持共享模式的子類不需要定義支持未使用模式的方法矮台。

??這個(gè)類定義了一個(gè)嵌套的ConditionObject類,可以用作Condition由子類實(shí)現(xiàn)支持獨(dú)占模式的方法isHeldExclusively報(bào)告同步是否只對(duì)當(dāng)前線程持有,release方法調(diào)用與getState獲取當(dāng)前的值和acquire方法完全釋放這個(gè)對(duì)象,鑒于這個(gè)保存的狀態(tài)值,最終將該對(duì)象恢復(fù)到以前獲取的狀態(tài)乏屯。AbstractQueuedSynchronizer沒有方法會(huì)創(chuàng)建這樣的條件,所以如果不能滿足這個(gè)約束瘦赫,就不要使用它辰晕。ConditionObject的行為當(dāng)然依賴于它的同步器實(shí)現(xiàn)的語義。

??該類為內(nèi)部隊(duì)列提供了檢查确虱、檢測(cè)和監(jiān)視方法含友,跟Condition對(duì)象的方法很相似⌒1纾可以使用AbstractQueuedSynchronizer將這些同步機(jī)制按照需要導(dǎo)出到類中窘问。

??該類的序列化只存儲(chǔ)底層原子整數(shù)維護(hù)狀態(tài),因此反序列化的對(duì)象具有空線程隊(duì)列宜咒。需要序列化的典型子類將定義一個(gè)readObject方法惠赫,該方法在反序列化時(shí)將其恢復(fù)到已知的初始狀態(tài)。

使用

??如果要使用這個(gè)類作為同步器的基礎(chǔ)故黑,可以使用getState儿咱、setState和/或compareAndSetState檢查和/或修改同步狀態(tài),根據(jù)需要重新定義以下方法:
?--tryAcquire
?--tryRelease
?--tryAcquireShared
?--tryReleaseShared
?--isHeldExclusively

??默認(rèn)情況下,每個(gè)方法都會(huì)UnsupportedOperationException场晶。這些方法的實(shí)現(xiàn)必須是內(nèi)部線程安全的混埠,并且通常應(yīng)該是短的,而不是阻塞的诗轻。定義這些方法是使用這個(gè)類的only支持的方法钳宪。所有其他方法都聲明為final,因?yàn)樗鼈儾荒塥?dú)立更改。

??您還可能發(fā)現(xiàn)從AbstractOwnableSynchronizer繼承的方法對(duì)于跟蹤擁有獨(dú)占同步器的線程非常有用使套。我們鼓勵(lì)您使用它們——這使得監(jiān)視和診斷工具能夠幫助用戶確定哪些線程持有鎖。

??即使這個(gè)類基于內(nèi)部FIFO隊(duì)列鞠柄,它也不會(huì)自動(dòng)執(zhí)行FIFO獲取策略侦高。獨(dú)占同步的核心形式為:

Acquire:
    while (!tryAcquire(arg)) {
        //enqueue thread if it is not already queued;
        //possibly block current thread;
    }
Release:
    if (tryRelease(arg))
        //unblock the first queued thread;

(共享模式類似,但可能涉及級(jí)聯(lián)信號(hào)厌杜。)

??因?yàn)楹炄氆@取是在排隊(duì)之前調(diào)用的奉呛,所以一個(gè)新的獲取線程可能會(huì)“插入”在其他被阻塞和排隊(duì)的線程之前。但是夯尽,如果需要瞧壮,您可以通過內(nèi)部調(diào)用一個(gè)或多個(gè)檢查方法來定義tryAcquire和/或tryAcquireShared來禁止插入,從而提供一個(gè)"公平" FIFO獲取順序匙握。特別是咆槽,如果hasQueuedPredecessors(一個(gè)專門為公平同步器設(shè)計(jì)的方法)返回true,那么大多數(shù)公平同步器可以定義tryAcquire來返回false圈纺。其他變化是可能的秦忿。

??對(duì)于默認(rèn)的barging(也稱為"貪心")策略,吞吐量和可伸縮性通常是最高的蛾娶。雖然不能保證這是公平的或無中斷的灯谣,但允許較早排隊(duì)的線程在較晚排隊(duì)的線程之前重新競(jìng)爭(zhēng),而且每次重新競(jìng)爭(zhēng)都有公平的機(jī)會(huì)面對(duì)傳入成功的線程蛔琅。而且胎许,在獲得的同時(shí)不<轉(zhuǎn)動(dòng)>。通常情況下罗售,在阻塞之前辜窑,它們可以執(zhí)行tryAcquire的多次調(diào)用,其間穿插著其他計(jì)算莽囤。當(dāng)獨(dú)占同步只被短暫地持有時(shí)谬擦,這就提供了自旋的大部分好處,而當(dāng)它不被持有時(shí)朽缎,就沒有了大部分的不利因素惨远。如果需要,您可以通過前面的調(diào)用來增強(qiáng)這種能力话肖,通過“fast-path”檢查來獲取方法北秽,可能會(huì)預(yù)先檢查hasContended和/或hasQueuedThreads,只在同步器可能不存在競(jìng)爭(zhēng)的情況下才能這樣做最筒。

??這個(gè)類為同步提供了一個(gè)高效且可伸縮的基礎(chǔ)贺氓,部分原因是它將同步器的使用范圍專門化為可以依賴于int狀態(tài)、獲取和釋放參數(shù)以及內(nèi)部FIFO等待隊(duì)列的同步器床蜘。當(dāng)這還不夠時(shí)辙培,您可以使用java.util.concurrent.atomic類蔑水、自定義的java.util.Queue類和LockSupport阻塞支持,從較低的級(jí)別構(gòu)建同步器扬蕊。

使用舉例

??這是一個(gè)不可重入互斥鎖類搀别,它使用0表示未鎖狀態(tài),1表示已鎖狀態(tài)尾抑。雖然不可重入鎖并不嚴(yán)格要求記錄當(dāng)前的持有線程歇父,但是這個(gè)類還是這樣做了,以便更容易地監(jiān)視使用情況再愈。它還支持conditions榜苫,并公開了一種檢測(cè)方法:

class Mutex implements Lock, java.io.Serializable {
    // 我們的內(nèi)部幫助類
    private static class Sync extends AbstractQueuedSynchronizer {
        // 報(bào)告是否處于鎖定狀態(tài)
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

    // 如果狀態(tài)為0,則獲取鎖
    public boolean tryAcquire(int acquires) {
        assert acquires == 1; // Otherwise unused
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    // 通過把狀態(tài)設(shè)為0來釋放鎖
    protected boolean tryRelease(int releases) {
        assert releases == 1; // Otherwise unused
        if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
 
    // 提供一個(gè) Condition
    Condition newCondition() { return new ConditionObject(); }
 
    // 反序列化
    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }

 
    // sync對(duì)象完成所有的工作翎冲。我們只需要用它轉(zhuǎn)發(fā)功能就行垂睬。
    private final Sync sync = new Sync();
 
    public void lock()                { sync.acquire(1); }
    public boolean tryLock()          { return sync.tryAcquire(1); }
    public void unlock()              { sync.release(1); }
    public Condition newCondition()   { return sync.newCondition(); }
    public boolean isLocked()         { return sync.isHeldExclusively(); }
    public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}

??這是一個(gè)類似于java.util.concurrent.CountDownLatch的鎖存器類。區(qū)別是它只需要一個(gè)signal來觸發(fā)府适。因?yàn)殒i存器是非獨(dú)占的羔飞,它使用shared獲取和釋放方法。

class BooleanLatch {
    private static class Sync extends AbstractQueuedSynchronizer {
        boolean isSignalled() { return getState() != 0; }
        protected int tryAcquireShared(int ignore) {
            return isSignalled() ? 1 : -1;
        }
        protected boolean tryReleaseShared(int ignore) {
            setState(1);
            return true;
        }
    }
    private final Sync sync = new Sync();
    public boolean isSignalled() { return sync.isSignalled(); }
    public void signal()         { sync.releaseShared(1); }
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
}

AbstractQueuedSynchronizer.Node

??等待隊(duì)列節(jié)點(diǎn)類檐春。

??等待隊(duì)列是CLH(Craig逻淌、Landin和Hagersten)鎖隊(duì)列的變體。CLH鎖通常用于自旋鎖疟暖。相反卡儒,我們使用它們來阻塞同步器,但使用相同的基本策略俐巴,即在其節(jié)點(diǎn)的前身中保存關(guān)于線程的一些控制信息骨望。每個(gè)節(jié)點(diǎn)中的status字段跟蹤線程是否應(yīng)該阻塞。一個(gè)節(jié)點(diǎn)在其前驅(qū)節(jié)點(diǎn)釋放時(shí)發(fā)出信號(hào)欣舵。否則擎鸠,隊(duì)列的每個(gè)節(jié)點(diǎn)都充當(dāng)持有單個(gè)等待線程的特定通知樣式的監(jiān)視器。狀態(tài)字段不控制線程是否被授予鎖等缘圈。如果線程是隊(duì)列中的第一個(gè)線程劣光,它可以嘗試獲取。但第一個(gè)也并不能保證成功糟把,它只給你競(jìng)爭(zhēng)的權(quán)利绢涡。因此,當(dāng)前發(fā)布的競(jìng)爭(zhēng)者線程可能需要重新等待遣疯。

??要入隊(duì)一個(gè)CLH鎖雄可,您需要將其原子拼接為新尾部。 要出列,您只需設(shè)置head字段数苫。

      +------+  prev +-----+       +-----+
 head |      | <---- |     | <---- |     |  tail
      +------+       +-----+       +-----+

??插入CLH隊(duì)列只需要對(duì)“尾部”進(jìn)行單個(gè)原子操作聪舒,因此存在從未排隊(duì)到排隊(duì)的簡(jiǎn)單原子點(diǎn)劃分。 同樣虐急,出列只涉及更新“頭部”过椎。 但是,節(jié)點(diǎn)需要更多的工作來確定他們的后驅(qū)是誰戏仓,部分是為了處理由于超時(shí)和中斷而可能的取消。

??處理取消主要需要“prev”鏈接(未在原始CLH鎖中使用)亡鼠。 如果節(jié)點(diǎn)被取消赏殃,則其后驅(qū)(通常)重新鏈接到未取消的前驅(qū)。 有關(guān)自旋鎖的類似機(jī)制的解釋间涵,請(qǐng)參閱Scott和Scherer的論文
http://www.cs.rochester.edu/u/scott/synchronization/

??我們還使用next鏈接來實(shí)現(xiàn)阻塞機(jī)制仁热。 每個(gè)節(jié)點(diǎn)保存了自己所在線程ID,因此前驅(qū)通過遍歷下一個(gè)鏈接來通知下一個(gè)節(jié)點(diǎn)以確定它是哪個(gè)線程勾哩。 后驅(qū)的確定必須避免使用新排隊(duì)節(jié)點(diǎn)的競(jìng)爭(zhēng)來設(shè)置其前驅(qū)的next字段抗蠢。 必要時(shí),當(dāng)節(jié)點(diǎn)的后驅(qū)看起來為空時(shí)思劳,通過從原子更新的tail向后檢查來解決這個(gè)問題迅矛。 (或者,換句話說潜叛,下一個(gè)鏈接是一個(gè)優(yōu)化秽褒,因此我們通常不需要向后掃描。)

??消除為基本算法引入了一些保守性威兜。 由于我們必須輪詢消除其他節(jié)點(diǎn)销斟,我們可能會(huì)忽略已消除的節(jié)點(diǎn)是在我們前面還是在我們后面。 這一點(diǎn)通過消除時(shí)始終取消停車的繼承人來處理椒舵,允許他們穩(wěn)定在新的前驅(qū)蚂踊,除非我們能夠確定一位將承擔(dān)此責(zé)任的未經(jīng)解除的前驅(qū)。

??CLH隊(duì)列需要一個(gè)虛擬標(biāo)頭節(jié)點(diǎn)才能開始笔宿。 但是我們不會(huì)在構(gòu)造函數(shù)里創(chuàng)建它們犁钟,因?yàn)槿绻麤]有調(diào)用就會(huì)浪費(fèi)資源。 相反措伐,節(jié)點(diǎn)在第一次調(diào)用時(shí)被構(gòu)造特纤,并且設(shè)置頭尾指針。

??Conditions等待的線程使用相同的節(jié)點(diǎn)侥加,但使用不同鏈接捧存。Conditions只需要鏈接簡(jiǎn)單(非并發(fā))鏈接隊(duì)列中的節(jié)點(diǎn),因?yàn)樗鼈儍H在完全持有時(shí)才被訪問。 等待時(shí)昔穴,將節(jié)點(diǎn)插入條件隊(duì)列镰官。 根據(jù)信號(hào),節(jié)點(diǎn)被轉(zhuǎn)移到主隊(duì)列吗货。 狀態(tài)字段的特殊值用于標(biāo)記節(jié)點(diǎn)所在的隊(duì)列泳唠。

??感謝Dave Dice,Mark Moir宙搬,Victor Luchangco笨腥,Bill Scherer和Michael Scott以及JSR-166專家組成員對(duì)本課程設(shè)計(jì)的有益想法,討論和批評(píng)勇垛。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末脖母,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子闲孤,更是在濱河造成了極大的恐慌谆级,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讼积,死亡現(xiàn)場(chǎng)離奇詭異肥照,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)勤众,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門舆绎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人们颜,你說我怎么就攤上這事亿蒸。” “怎么了掌桩?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵边锁,是天一觀的道長。 經(jīng)常有香客問我波岛,道長茅坛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任则拷,我火速辦了婚禮贡蓖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘煌茬。我一直安慰自己斥铺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布坛善。 她就那樣靜靜地躺著晾蜘,像睡著了一般邻眷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剔交,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天肆饶,我揣著相機(jī)與錄音,去河邊找鬼岖常。 笑死驯镊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的竭鞍。 我是一名探鬼主播板惑,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼偎快!你這毒婦竟也來了洒放?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤滨砍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后妖异,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惋戏,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年他膳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了响逢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棕孙,死狀恐怖舔亭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蟀俊,我是刑警寧澤钦铺,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站肢预,受9級(jí)特大地震影響矛洞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烫映,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一沼本、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锭沟,春花似錦抽兆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凭涂。三九已至,卻和暖如春厉熟,著一層夾襖步出監(jiān)牢的瞬間导盅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來泰國打工揍瑟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留白翻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓绢片,卻偏偏與公主長得像滤馍,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子底循,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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

  • ReentrantLock 介紹 一個(gè)可重入的互斥鎖巢株,它具有與使用{synchronized}方法和語句訪問的隱式...
    tomas家的小撥浪鼓閱讀 4,045評(píng)論 1 4
  • 基于jdk8 越是核心的東西越是要反復(fù)看,本文篇幅較長熙涤,希望各位細(xì)細(xì)品讀阁苞,來回多讀幾遍理解下。 AQS簡(jiǎn)介 ??j...
    劍書藏于西閱讀 433評(píng)論 0 2
  • AQS是JUC鎖框架中最重要的類祠挫,通過它來實(shí)現(xiàn)獨(dú)占鎖和共享鎖的那槽。本章是對(duì)AbstractQueuedSynchro...
    wo883721閱讀 13,017評(píng)論 5 43
  • 90天目標(biāo) 1.生物鐘理念:實(shí)現(xiàn)到點(diǎn)睡覺到點(diǎn)醒,到點(diǎn)吃飯等舔,到點(diǎn)親子骚灸,到點(diǎn)娛樂到點(diǎn)停的狀態(tài)。 2.跑步:完成百日跑打...
    erinking閱讀 100評(píng)論 0 0
  • 墨記·日歌(壹佰肆拾叁·2018.9.30) 睡夢(mèng)之野 1 當(dāng)時(shí)間以埋葬的方式 解決了所有的問題 醒來或者睡去 都...
    湖北葉瀟閱讀 189評(píng)論 3 4