Java并發(fā)——讀寫鎖

讀寫鎖在同一時刻可以允許多個讀線程訪問,但是在寫線程訪問時针炉,所有的讀
線程和其他寫線程均被阻塞督勺。讀寫鎖維護了一對鎖渠羞,一個讀鎖和一個寫鎖,通過分離讀鎖和寫鎖智哀,使得并發(fā)性相比一般的排他鎖有了很大提升次询。

一般情況下,讀寫鎖的性能都會比排它鎖好瓷叫,因為大多數(shù)場景讀是多于寫的屯吊。在讀多于寫的情況下,讀寫鎖能夠提供比排它鎖更好的并發(fā)性和吞吐量摹菠。Java并發(fā)包提供讀寫鎖的實現(xiàn)是ReentrantReadWriteLock盒卸,它提供的特性如表所示:

ReentrantReadWriteLock的特性

讀寫鎖的實現(xiàn)分析

1 讀寫狀態(tài)的設(shè)計

讀寫鎖同樣依賴自定義同步器來實現(xiàn)同步功能,而讀寫狀態(tài)就是其同步器的同步狀態(tài)次氨。讀寫鎖的自定義同步器需要在同步狀態(tài)(一個整型變量)上維護多個讀線程和一個寫線程的狀態(tài)蔽介,使得該狀態(tài)的設(shè)計成為讀寫鎖實現(xiàn)的關(guān)鍵。

如果在一個整型變量上維護多種狀態(tài)煮寡,就一定需要“按位切割使用”這個變量虹蓄,讀寫鎖將變量切分成了兩個部分,高16位表示讀幸撕,低16位表示寫薇组,劃分方式如圖所示:

讀寫鎖狀態(tài)的劃分方式

讀寫鎖是如何迅速確定讀和寫各自的狀態(tài)呢?答案是通過位運算杈帐。假設(shè)當(dāng)前同步狀態(tài)值為S体箕,寫狀態(tài)等于S&0x0000FFFF(將高16位全部抹去),讀狀態(tài)等于S>>>16(無符號補0右移16位)挑童。當(dāng)寫狀態(tài)增加1時累铅,等于S+1,當(dāng)讀狀態(tài)增加1時站叼,等于S+(1<<16)娃兽,也就是S+0x00010000。

根據(jù)狀態(tài)的劃分能得出一個推論:S不等于0時尽楔,當(dāng)寫狀態(tài)(S&0x0000FFFF)等于0時投储,則讀狀態(tài)(S>>>16)大于0第练,即讀鎖已被獲取。

2 寫鎖的獲取與釋放

寫鎖是一個支持重進入的排它鎖玛荞,獲取情況有兩種

  • 如果當(dāng)前線程已經(jīng)獲取了寫鎖娇掏,則增加寫狀態(tài)。
  • 如果當(dāng)前線程在獲取寫鎖時勋眯,讀鎖已經(jīng)被獲扔の唷(讀狀態(tài)不為0)或者該線程不是已經(jīng)獲取寫鎖的線程,則當(dāng)前線程進入等待狀態(tài)客蹋,代碼如下所示:
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        // 存在讀鎖或者當(dāng)前獲取線程不是已經(jīng)獲取寫鎖的線程
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 增加寫鎖狀態(tài)
        setState(c + acquires);
        return true;
    }
    if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) {
        return false;
    }
    setExclusiveOwnerThread(current);
    return true;
}

3 讀鎖的獲取與釋放

讀鎖是一個支持重進入的共享鎖塞蹭,它能夠被多個線程同時獲取。

  • 在沒有其他寫線程訪問(或者寫狀態(tài)為0)時讶坯,讀鎖總會被成功地獲取番电,而所做的也只是(線程安全的)增加讀狀態(tài)
  • 如果當(dāng)前線程已經(jīng)獲取了讀鎖,則增加讀狀態(tài)辆琅。
  • 如果當(dāng)前線程在獲取讀鎖時漱办,寫鎖已被其他線程獲取,則進入等待狀態(tài)涎跨。
protected final int tryAcquireShared(int unused) {
    for (;;) {
        int c = getState();
        int nextc = c + (1 << 16);
        if (nextc < c)
            throw new Error("Maximum lock count exceeded");
        // 如果當(dāng)前線程不是獲取寫鎖的線程洼冻,則獲取讀鎖失敗
        if (exclusiveCount(c) != 0 && owner != Thread.currentThread())
            return -1;
        // 如果當(dāng)前線程是獲取寫鎖的線程(鎖降級) 
        // 或?qū)戞i未被線程獲取,則獲取讀鎖
        if (compareAndSetState(c, nextc))
            return 1;
    }
}

4 鎖降級

如果當(dāng)前線程擁有寫鎖隅很,然后將其釋放撞牢,最后再獲取讀鎖,這種分段完成的過程不能稱之為鎖降級叔营。

鎖降級是指把持孜荼搿(當(dāng)前擁有的)寫鎖,再獲取到讀鎖绒尊,隨后釋放(先前擁有的)寫鎖的過程畜挥。

那么鎖降級的設(shè)計的目的是什么呢?為何要在擁有寫鎖的前提下去獲取讀鎖婴谱?
通過查看一些文章蟹但,寫一下自己的理解:

鎖降級的目的其實是為了讓線程對數(shù)據(jù)變化敏感,如果先釋放寫鎖谭羔,再獲取讀鎖华糖,可能在獲取之前,會有其他線程獲取到寫鎖瘟裸,阻塞讀鎖的獲取客叉,就無法感知數(shù)據(jù)變化了。所以需要先hold住寫鎖,保證數(shù)據(jù)無變化兼搏,獲取讀鎖卵慰,然后再釋放寫鎖。

例如有多個線程對同一塊數(shù)據(jù)區(qū)域data進行讀寫操作佛呻,要求對每次數(shù)據(jù)的更改敏感裳朋。假設(shè)t1時刻data區(qū)域被寫線程將狀態(tài)s0更改為s1,更改完后若直接釋放鎖吓著,那么可能會有其他線程獲取寫鎖再扭,將data區(qū)域的狀態(tài)從s1更改為s2,這樣一來整個過程就無法感知到data區(qū)域的s1狀態(tài)夜矗。

如果采用了鎖降級,那么獲取寫鎖的線程t將data區(qū)域狀態(tài)更改為s1后便持有讀鎖让虐,那么其它想獲取寫鎖的線程將會阻塞紊撕,直到線程t將讀鎖釋放,那么這個過程中將會感知到data區(qū)域的s1狀態(tài)赡突。

鎖降級對比
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末对扶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子惭缰,更是在濱河造成了極大的恐慌浪南,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漱受,死亡現(xiàn)場離奇詭異络凿,居然都是意外死亡,警方通過查閱死者的電腦和手機昂羡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門絮记,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人虐先,你說我怎么就攤上這事怨愤。” “怎么了蛹批?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵撰洗,是天一觀的道長。 經(jīng)常有香客問我腐芍,道長差导,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任甸赃,我火速辦了婚禮柿汛,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己络断,他們只是感情好裁替,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著貌笨,像睡著了一般弱判。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锥惋,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天昌腰,我揣著相機與錄音,去河邊找鬼膀跌。 笑死遭商,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捅伤。 我是一名探鬼主播劫流,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼丛忆!你這毒婦竟也來了祠汇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤熄诡,失蹤者是張志新(化名)和其女友劉穎可很,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體凰浮,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡我抠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了导坟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屿良。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖惫周,靈堂內(nèi)的尸體忽然破棺而出尘惧,到底是詐尸還是另有隱情,我是刑警寧澤递递,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布喷橙,位于F島的核電站,受9級特大地震影響登舞,放射性物質(zhì)發(fā)生泄漏贰逾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一菠秒、第九天 我趴在偏房一處隱蔽的房頂上張望疙剑。 院中可真熱鬧氯迂,春花似錦、人聲如沸言缤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽管挟。三九已至轿曙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間僻孝,已是汗流浹背导帝。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留穿铆,地道東北人您单。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像荞雏,于是被迫代替她去往敵國和親睹限。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354

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