幾種常見的線程鎖

http://www.reibang.com/p/d2ac26ca6525


悲觀鎖與樂觀鎖:

悲觀鎖(多鎖莺掠,適合寫多职恳,競(jìng)爭(zhēng)激烈的場(chǎng)景)

悲觀(先取鎖再訪問):每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖地粪,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)block直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖遭庶,表鎖等,讀鎖稠屠,寫鎖等峦睡,都是在做操作之前先上鎖。它指的是對(duì)數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù)权埠,以及來自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度榨了,因此,在整個(gè)數(shù)據(jù)處理過程中攘蔽,將數(shù)據(jù)處于鎖定狀態(tài)龙屉。悲觀鎖的實(shí)現(xiàn),往往依靠數(shù)據(jù)庫提供的鎖機(jī)制(也只有數(shù)據(jù)庫層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問的排他性满俗,否則转捕,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制,也無法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))唆垃。

    • MySQL中的應(yīng)用:

使用悲觀鎖五芝,必須關(guān)閉MySQL數(shù)據(jù)庫的自動(dòng)提交屬性。因?yàn)镸ySQL默認(rèn)使用autocommit模式降盹,也就是說与柑,當(dāng)執(zhí)行一個(gè)更新操作后,MySQL會(huì)立刻將結(jié)果進(jìn)行提交(sql:set autocommit=0)蓄坏。注意价捧,鎖只有在執(zhí)行 COMMIT或者ROLLBACK的時(shí)候才會(huì)釋放,并且所有的鎖是在同一時(shí)刻被釋放涡戳。

begin;
// 查詢上鎖
select quantity from items where id=1 for update;
// 修改庫存
update items set quantity where id =1;
// 提交
commit;

在對(duì)id = 1的記錄修改前结蟋,先通過for update的方式進(jìn)行加鎖,然后再進(jìn)行修改渔彰。這就是比較典型的悲觀鎖策略嵌屎。

  • 悲觀鎖主要分為共享鎖或排他鎖

(1)共享鎖【Shared lock】又稱為讀鎖,簡(jiǎn)稱S鎖恍涂。顧名思義宝惰,共享鎖就是多個(gè)事務(wù)對(duì)于同一數(shù)據(jù)可以共享一把鎖,都能訪問到數(shù)據(jù)再沧,但是只能讀不能修改尼夺。
(2)排他鎖【Exclusive lock】又稱為寫鎖,簡(jiǎn)稱X鎖。顧名思義淤堵,排他鎖就是不能與其他鎖并存寝衫,如果一個(gè)事務(wù)獲取了一個(gè)數(shù)據(jù)行的排他鎖,其他事務(wù)就不能再獲取該行的其他鎖拐邪,包括共享鎖和排他鎖慰毅,但是獲取排他鎖的事務(wù)是可以對(duì)數(shù)據(jù)行讀取和修改。


樂觀鎖(少鎖扎阶,適用于寫少汹胃,競(jìng)爭(zhēng)不激烈的應(yīng)用場(chǎng)景,可以提高吞吐量)

樂觀鎖是相對(duì)悲觀鎖而言的乘陪,樂觀鎖假設(shè)數(shù)據(jù)一般情況下不會(huì)造成沖突统台,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測(cè)啡邑,如果發(fā)現(xiàn)沖突了贱勃,則返回給用戶錯(cuò)誤的信息,讓用戶決定如何去做谤逼。

  • 樂觀鎖的例子:
select quantity from items where id=1;
// 判斷不變?cè)傩薷?update items set quantity =2 where id =1 and quantity =3;

因?yàn)閡pdate操作會(huì)上排他鎖

  • 樂觀鎖的一些實(shí)現(xiàn)方法:

(1)使用數(shù)據(jù)版本(Version++)記錄機(jī)制實(shí)現(xiàn)贵扰,這是樂觀鎖最常用的一種實(shí)現(xiàn)方式。何謂數(shù)據(jù)版本流部?即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí)戚绕,一般是通過為數(shù)據(jù)庫表增加一個(gè)數(shù)字類型的 “version” 字段來實(shí)現(xiàn)。當(dāng)讀取數(shù)據(jù)時(shí)枝冀,將version字段的值一同讀出舞丛,數(shù)據(jù)每更新一次,對(duì)此version值加一果漾。當(dāng)我們提交更新的時(shí)候球切,判斷數(shù)據(jù)庫表對(duì)應(yīng)記錄的當(dāng)前版本信息與第一次取出來的version值進(jìn)行比對(duì),如果數(shù)據(jù)庫表當(dāng)前版本號(hào)與第一次取出來的version值相等绒障,則予以更新吨凑,否則認(rèn)為是過期數(shù)據(jù)。

update item;
set quantity=quantity-1 where id =1 and quantity - 1 > 0
// 防止商品庫存為0依舊-1户辱,quantity-1>0 相當(dāng)于樂觀鎖鸵钝,相比跟原值比較更適合高并發(fā)場(chǎng)景,因?yàn)樵敌薷奶l繁

(2)使用時(shí)間戳(timestamp)庐镐。樂觀鎖定的第二種實(shí)現(xiàn)方式和第一種差不多恩商,同樣是在需要樂觀鎖控制的table中增加一個(gè)字段,名稱無所謂必逆,字段類型使用時(shí)間戳(timestamp), 和上面的version類似痕届,也是在更新提交的時(shí)候檢查當(dāng)前數(shù)據(jù)庫中數(shù)據(jù)的時(shí)間戳和自己更新前取到的時(shí)間戳進(jìn)行對(duì)比韧献,如果一致則OK,否則就是版本沖突研叫。
(3)使用原值與當(dāng)前值對(duì)比,類似之前提的璧针。

    • MySQL中的注意要點(diǎn):
InnoDB默認(rèn)行級(jí)鎖嚷炉。行級(jí)鎖都是基于索引的,如果一條SQL語句用不到索引是不會(huì)使用行級(jí)鎖的探橱,會(huì)使用表級(jí)鎖把整張表鎖住申屹。
update操作會(huì)對(duì)操作對(duì)象加排他鎖,保證id對(duì)應(yīng)的行或者表無法被其他線程修改隧膏。
    • ABA問題:
一個(gè)線程A從數(shù)據(jù)庫中取出庫存數(shù)a==3哗讥,這時(shí)候另一個(gè)線程B也從數(shù)據(jù)庫中取出庫存數(shù)a==3,并且B進(jìn)行了一些操作a變成了2胞枕,
然后B又將庫存數(shù)a變成3杆煞,這時(shí)候線程A進(jìn)行CAS操作發(fā)現(xiàn)數(shù)據(jù)庫中a仍然是3,然后A操作成功腐泻。盡管線程A的CAS操作成功决乎,
但是不代表這個(gè)過程就是沒有問題的。

JAVA中的樂觀鎖與悲觀鎖:

  • CAS(一種樂觀鎖):比如Atomic派桩,更新一個(gè)變量的時(shí)候构诚,只有當(dāng)變量的原值A(chǔ)和內(nèi)存地址V當(dāng)中的實(shí)際值相同時(shí),才會(huì)將內(nèi)存地址V對(duì)應(yīng)的值修改為B铆惑。(CAS 基于硬件實(shí)現(xiàn)范嘱,不需要進(jìn)入內(nèi)核,不需要切換線程)

問題1:CPU開銷較大(線程沖突嚴(yán)重時(shí)樂觀鎖的自旋)
在并發(fā)量比較高的情況下员魏,如果許多線程反復(fù)嘗試更新某一個(gè)變量丑蛤,卻又一直更新不成功,循環(huán)往復(fù)逆趋,會(huì)給CPU帶來很大的壓力盏阶。

問題2:不能保證代碼塊的原子性
CAS機(jī)制所保證的只是一個(gè)變量的原子性操作,而不能保證整個(gè)代碼塊的原子性闻书。比如需要保證3個(gè)變量共同進(jìn)行原子性的更新名斟,就不得不使用Synchronized了。

  • synchronized(一種悲觀鎖):用于修飾類魄眉,代碼塊砰盐,方法
    Synchronized會(huì)讓沒有得到鎖資源的線程進(jìn)入BLOCKED狀態(tài),而后在爭(zhēng)奪到鎖資源后恢復(fù)為RUNNABLE狀態(tài)坑律,這個(gè)過程中涉及到操作系統(tǒng)用戶模式和內(nèi)核模式的轉(zhuǎn)換岩梳,代價(jià)比較高。
// 對(duì)象鎖
public class Test
{
    // 對(duì)象鎖:形式1(方法鎖)
    public synchronized void Method1()
    {
        System.out.println("我是對(duì)象鎖也是方法鎖");
        try
        {
            Thread.sleep(500);
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }
}

    // 對(duì)象鎖:形式2(代碼塊形式)
    public void Method2()
    {
        synchronized (this)
        {
            System.out.println("我是對(duì)象鎖");
            try
            {
                Thread.sleep(500);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
 }

對(duì)象鎖是用來控制實(shí)例方法之間的同步,類鎖是用來控制靜態(tài)方法(或靜態(tài)變量互斥體)之間的同步冀值。

public class Test
{
   // 類鎖:形式1
    public static synchronized void Method1()
    {
        System.out.println("我是類鎖一號(hào)");
        try
        {
            Thread.sleep(500);
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    // 類鎖:形式2
    public void Method2()
    {
        synchronized (Test.class)
        {
            System.out.println("我是類鎖二號(hào)");
            try
            {
                Thread.sleep(500);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }
}

公平鎖與非公平鎖:

在非公平鎖策略之下也物,不一定First in First Serve,而是出現(xiàn)各種線程隨意搶占的情況列疗。默認(rèn)的ReentrantLock對(duì)象構(gòu)造出來就是非公平的滑蚯。當(dāng)線程被掛起排隊(duì)等待的時(shí)候,會(huì)被其他線程趁著線程還未被喚醒的空擋搶占鎖(將state置為1)抵栈。

在構(gòu)造ReentrantLock對(duì)象的時(shí)候傳入一個(gè)true即可獲得公平鎖告材。公平鎖會(huì)在搶占前先查詢隊(duì)列中是否有線程在等待,如果有就加入等待隊(duì)列古劲,如果沒有就直接執(zhí)行斥赋。
ReentrantLock lock = new ReentrantLock(true)

  • 優(yōu)劣:在CPU線程狀態(tài)切換的空擋期較長(zhǎng)的情況下,非公平鎖性能高于公平鎖性能产艾。首先疤剑,在恢復(fù)一個(gè)被掛起的線程與該線程真正運(yùn)行之間存在著嚴(yán)重的延遲。而且胰舆,非公平鎖能更充分的利用cpu的時(shí)間片骚露,盡量的減少cpu空閑的狀態(tài)時(shí)間。

自旋鎖:

當(dāng)一個(gè)線程在獲取鎖的時(shí)候缚窿,如果鎖已經(jīng)被其它線程獲取棘幸,那么該線程將循環(huán)等待,然后不斷的判斷鎖是否能夠被成功獲取倦零,直到獲取到鎖才會(huì)退出循環(huán)误续。


互斥鎖 mutex:

在訪問共享資源之前對(duì)進(jìn)行加鎖操作,在訪問完成之后進(jìn)行解鎖操作扫茅。
加鎖后蹋嵌,任何其他試圖再次加鎖的線程會(huì)被阻塞,直到當(dāng)前進(jìn)程解鎖葫隙。
如果解鎖時(shí)有一個(gè)以上的線程阻塞栽烂,那么所有該鎖上的線程都被編程就緒狀態(tài),
第一個(gè)變?yōu)榫途w狀態(tài)的線程又執(zhí)行加鎖操作恋脚,那么其他的線程又會(huì)進(jìn)入等待腺办。
在這種方式下,只有一個(gè)線程能夠訪問被互斥鎖保護(hù)的資源糟描。


讀寫鎖 rwlock:

(也叫作共享互斥鎖:讀模式共享怀喉,寫模式互斥)

  • 適用場(chǎng)景:
    讀寫鎖非常適合對(duì)數(shù)據(jù)結(jié)構(gòu)讀的次數(shù)遠(yuǎn)遠(yuǎn)大于寫的情況。

一次只有一個(gè)線程可以占有寫模式的讀寫鎖船响,但是多個(gè)線程可以同時(shí)占有讀模式的讀寫鎖躬拢。(這也是它能夠?qū)崿F(xiàn)高并發(fā)的一種手段) 但是進(jìn)行讀操作的時(shí)候不能進(jìn)行寫操作躲履。所以當(dāng)讀者源源不斷到來的時(shí)候,寫者總是得不到讀寫鎖聊闯,就會(huì)造成不公平的狀態(tài)工猜。

當(dāng)處于讀模式的讀寫鎖接收到一個(gè)試圖對(duì)其進(jìn)行寫模式加鎖操作時(shí),便會(huì)阻塞后面對(duì)其進(jìn)行讀模式加鎖操作的線程菱蔬。 這樣等到已經(jīng)加讀模式的鎖解鎖后域慷,寫進(jìn)程能夠訪問此鎖保護(hù)的資源。

  • 解決方案:

當(dāng)處于讀模式的讀寫鎖接收到一個(gè)試圖對(duì)其進(jìn)行寫模式加鎖操作時(shí)汗销,便會(huì)阻塞后面對(duì)其進(jìn)行讀模式加鎖操作的線程。 這樣等到已經(jīng)加讀模式的鎖解鎖后抵窒,寫進(jìn)程能夠訪問此鎖保護(hù)的資源弛针。


RCU鎖(Read-Copy Update):

讀-復(fù)制 更新

RCU中,讀者不需要使用鎖李皇,要訪問資源盡管訪問就好了削茁。
RCU中,寫者的同步開銷比較大掉房,要等到所有的讀者都訪問完成了才能夠?qū)Ρ槐Wo(hù)的資源進(jìn)行更新茧跋。

寫者修改數(shù)據(jù)前首先拷貝一個(gè)被修改元素的副本,然后在副本上進(jìn)行修改卓囚,修改完畢后它向垃圾回收器注冊(cè)一個(gè)回調(diào)函數(shù)以便在適當(dāng)?shù)臅r(shí)機(jī)執(zhí)行真正的修改操作瘾杭。

讀者必須提供一個(gè)信號(hào)給寫者以便寫者能夠確定數(shù)據(jù)可以被安全地釋放或修改的時(shí)機(jī)。有一個(gè)專門的垃圾收集器來探測(cè)讀者的信號(hào)哪亿,一旦所有的讀者都已經(jīng)發(fā)送信號(hào)告知它們都不在使用被RCU保護(hù)的數(shù)據(jù)結(jié)構(gòu)粥烁,垃圾收集器就調(diào)用回調(diào)函數(shù)完成最后的數(shù)據(jù)釋放或修改操作。


Java的偏向鎖和輕量級(jí)鎖

(1)重量級(jí)鎖:
Synchronized是通過對(duì)象內(nèi)部的一個(gè)叫做監(jiān)視器鎖(monitor)來實(shí)現(xiàn)的蝇棉。但是監(jiān)視器鎖本質(zhì)又是依賴于底層的操作系統(tǒng)的Mutex Lock來實(shí)現(xiàn)的讨阻。而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換這就需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)成本非常高篡殷,狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間钝吮,這就是為什么Synchronized效率低的原因。因此板辽,這種依賴于操作系統(tǒng)Mutex Lock所實(shí)現(xiàn)的鎖我們稱之為“重量級(jí)鎖”奇瘦。JDK中對(duì)Synchronized做的種種優(yōu)化,其核心都是為了減少這種重量級(jí)鎖的使用戳气。JDK1.6以后链患,為了減少獲得鎖和釋放鎖所帶來的性能消耗,提高性能瓶您,引入了“輕量級(jí)鎖”和“偏向鎖”麻捻。

鎖的狀態(tài)總共有四種:無鎖狀態(tài)纲仍、偏向鎖、輕量級(jí)鎖和重量級(jí)鎖贸毕。隨著鎖的競(jìng)爭(zhēng)郑叠,鎖可以從偏向鎖升級(jí)到輕量級(jí)鎖,再升級(jí)的重量級(jí)鎖(但是鎖的升級(jí)是單向的明棍,也就是說只能從低到高升級(jí)乡革,不會(huì)出現(xiàn)鎖的降級(jí))。JDK 1.6中默認(rèn)是開啟偏向鎖和輕量級(jí)鎖的摊腋,我們也可以通過-XX:-UseBiasedLocking來禁用偏向鎖沸版。鎖的狀態(tài)保存在對(duì)象的頭文件中,以32位的JDK為例:

image.png

輕量級(jí)鎖并不是用來代替重量級(jí)鎖的兴蒸,它的本意是在沒有多線程競(jìng)爭(zhēng)的前提下视粮,減少傳統(tǒng)的重量級(jí)鎖使用產(chǎn)生的性能消耗。在解釋輕量級(jí)鎖的執(zhí)行過程之前橙凳,先明白一點(diǎn)蕾殴,輕量級(jí)鎖所適應(yīng)的場(chǎng)景是線程交替執(zhí)行同步塊的情況,如果存在同一時(shí)間訪問同一鎖的情況岛啸,就會(huì)導(dǎo)致輕量級(jí)鎖膨脹為重量級(jí)鎖钓觉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市坚踩,隨后出現(xiàn)的幾起案子荡灾,更是在濱河造成了極大的恐慌,老刑警劉巖堕虹,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卧晓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡赴捞,警方通過查閱死者的電腦和手機(jī)逼裆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赦政,“玉大人胜宇,你說我怎么就攤上這事』肿牛” “怎么了桐愉?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)掰派。 經(jīng)常有香客問我从诲,道長(zhǎng),這世上最難降的妖魔是什么靡羡? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任系洛,我火速辦了婚禮俊性,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘描扯。我一直安慰自己定页,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布绽诚。 她就那樣靜靜地躺著典徊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪恩够。 梳的紋絲不亂的頭發(fā)上卒落,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音蜂桶,去河邊找鬼导绷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛屎飘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贾费,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼钦购,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了褂萧?” 一聲冷哼從身側(cè)響起押桃,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎导犹,沒想到半個(gè)月后唱凯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谎痢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年磕昼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片节猿。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡票从,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出滨嘱,到底是詐尸還是另有隱情峰鄙,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布太雨,位于F島的核電站吟榴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏囊扳。R本人自食惡果不足惜吩翻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一兜看、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仿野,春花似錦铣减、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至球涛,卻和暖如春劣针,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亿扁。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工捺典, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人从祝。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓襟己,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親牍陌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子擎浴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349