Java并发篇_乐观锁与悲观锁
樂觀鎖對(duì)應(yīng)于生活中樂觀的人總是想著事情往好的方向發(fā)展,悲觀鎖對(duì)應(yīng)于生活中悲觀的人總是想著事情往壞的方向發(fā)展。
一、引入概念
1、悲觀鎖
總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖(共享資源每次只給一個(gè)線程使用,其它線程阻塞,用完后再把資源轉(zhuǎn)讓給其它線程)。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨(dú)占鎖就是悲觀鎖思想的實(shí)現(xiàn)。
2、樂觀鎖
總是假設(shè)最好的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)機(jī)制和CAS算法實(shí)現(xiàn)。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫提供的類似于write_condition機(jī)制,其實(shí)都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實(shí)現(xiàn)方式CAS實(shí)現(xiàn)的。
3、兩種鎖的使用場景
從上面對(duì)兩種鎖的介紹,我們知道兩種鎖各有優(yōu)缺點(diǎn),不可認(rèn)為一種好于另一種,像樂觀鎖適用于寫比較少的情況下(多讀場景),即沖突真的很少發(fā)生的時(shí)候,這樣可以省去了鎖的開銷,加大了系統(tǒng)的整個(gè)吞吐量。但如果是多寫的情況,一般會(huì)經(jīng)常產(chǎn)生沖突,這就會(huì)導(dǎo)致上層應(yīng)用會(huì)不斷的進(jìn)行retry,這樣反倒是降低了性能,所以一般多寫的場景下用悲觀鎖就比較合適。
二、樂觀鎖的實(shí)現(xiàn)方式——CAS算法
1、CAS算法
什么是CAS?
CAS全稱為Compare And Swap即比較并交換,其算法公式如下:
函數(shù)公式:CAS(V,E,N)V:表示要更新的變量E:表示預(yù)期值N:表示新值
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-jpYccXsQ-1600049416983)(https://pics3.baidu.com/feed/810a19d8bc3eb135116a411f9e3739d6fc1f4497.jpeg?token=c57722e5bdc2b5d2e02a24affb2e2ac9&s=6AAC3C6203AEC5EF5CF530CE000080B1)]CAS
如果V值等于E值,則將V的值設(shè)為N。若V值和E值不同,則說明已經(jīng)有其他線程做了更新,則當(dāng)前線程什么都不做。通俗的理解就是CAS操作需要我們提供一個(gè)期望值,當(dāng)期望值與當(dāng)前線程的變量值相同時(shí),說明還沒線程修改該值,當(dāng)前線程可以進(jìn)行修改,也就是執(zhí)行CAS操作,但如果期望值與當(dāng)前線程不符,則說明該值已被其他線程修改,此時(shí)不執(zhí)行更新操作,但可以選擇重新讀取該變量再嘗試再次修改該變量,也可以放棄操作。
2、“ABA問題”與“版本號(hào)機(jī)制”
CAS 會(huì)導(dǎo)致“ABA 問題”。CAS 算法實(shí)現(xiàn)一個(gè)重要前提需要取出內(nèi)存中某時(shí)刻的數(shù)據(jù),而在下時(shí)刻比較并替換,那么在這個(gè)時(shí)間差類會(huì)導(dǎo)致數(shù)據(jù)的變化。
比如說一個(gè)線程 one 從內(nèi)存位置 V 中取出 A,這時(shí)候另一個(gè)線程 two 也從內(nèi)存中取出 A,并且two 進(jìn)行了一些操作變成了 B,然后 two 又將 V 位置的數(shù)據(jù)變成 A,這時(shí)候線程 one 進(jìn)行 CAS 操作發(fā)現(xiàn)內(nèi)存中仍然是 A,然后 one 操作成功。盡管線程 one 的 CAS 操作成功,但是不代表這個(gè)過程就是沒有問題的。
部分樂觀鎖的實(shí)現(xiàn)是通過版本號(hào)(version)的方式來解決 ABA 問題,樂觀鎖每次在執(zhí)行數(shù)據(jù)的修改操作時(shí),都會(huì)帶上一個(gè)版本號(hào),一旦版本號(hào)和數(shù)據(jù)的版本號(hào)一致就可以執(zhí)行修改操作并對(duì)版本號(hào)執(zhí)行+1 操作,否則就執(zhí)行失敗。因?yàn)槊看尾僮鞯陌姹咎?hào)都會(huì)隨之增加,所以不會(huì)出現(xiàn) ABA 問題,因?yàn)榘姹咎?hào)只會(huì)增加不會(huì)減少。
3、Java中的CAS
JDK5增加java.util.concurrent包,其中很多類使用了CAS操作。這些CAS操作基于Unsafe類中的native方法實(shí)現(xiàn):
//第一個(gè)參數(shù)o為給定對(duì)象,offset為對(duì)象內(nèi)存的偏移量,通過這個(gè)偏移量迅速定位字段并設(shè)置或獲取該字段的值, //expected表示期望值,x表示要設(shè)置的值,下面3個(gè)方法都通過CAS原子指令執(zhí)行操作, //設(shè)置成功返回true,否則返回false。 public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x); public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x); public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);由于CAS作用的對(duì)象在主存里而不是在線程的高速緩存里,CAS操作在Java中需要配合volatile使用。
Java中的CAS主要包含以下幾個(gè)問題:
- ABA問題,即變量V初次讀時(shí)是A值,被賦值時(shí)也是A值,但期間變量被賦值成B值,CAS會(huì)誤認(rèn)為他從沒被修改過。AtomicStampedReference和AtomicMarckableReference類提供了監(jiān)測ABA問題的能力,其中的compareAndSet方法首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前標(biāo)志等于預(yù)期標(biāo)志,全部相等則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。
- 循環(huán)開銷,自旋CAS長時(shí)間不成功會(huì)給CPU帶來非常大的執(zhí)行開銷。若JVM能支持pause命令,效率有一定提升。因?yàn)閜ause命令一方面可以延遲流水線執(zhí)行命令,使CPU不會(huì)消耗過多的執(zhí)行資源,另一方面可以避免退出循環(huán)時(shí)由內(nèi)存順序沖突引起的CPU流水線被沖突,從而提高CPU的執(zhí)行效率。
- 只能保證一個(gè)共享變量的原子操作,當(dāng)操作涉及跨多個(gè)共享變量時(shí)CAS無效。可用AtomicReference封裝多個(gè)字段來保證引用對(duì)象之間的原子性。
三、悲觀鎖——synchronized
1、synchronized
synchronized是Java中的關(guān)鍵字,是一種同步鎖。可修飾實(shí)例方法,靜態(tài)方法,代碼塊。
- 修飾實(shí)例方法:對(duì)當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖
- 修飾靜態(tài)方法:對(duì)當(dāng)前類對(duì)象加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類對(duì)象的鎖
- 修飾代碼塊:指定加鎖對(duì)象,對(duì)給定對(duì)象加鎖,進(jìn)入同步代碼庫前要獲得給定對(duì)象的鎖。
2、CAS與synchronized的使用情景
- 簡單的來說CAS適用于寫比較少的情況下(多讀場景,沖突一般較少),
- synchronized適用于寫比較多的情況下(多寫場景,沖突一般較多)
- 對(duì)于資源競爭較少(線程沖突較輕)的情況,使用synchronized同步鎖進(jìn)行線程阻塞和喚醒切換以及用戶態(tài)內(nèi)核態(tài)間的切換操作額外浪費(fèi)消耗cpu資源;而CAS基于硬件實(shí)現(xiàn),不需要進(jìn)入內(nèi)核,不需要切換線程,操作自旋幾率較少,因此可以獲得更高的性能。
- 對(duì)于資源競爭嚴(yán)重(線程沖突嚴(yán)重)的情況,CAS自旋的概率會(huì)比較大,從而浪費(fèi)更多的CPU資源,效率低于synchronized。
補(bǔ)充: Java并發(fā)編程這個(gè)領(lǐng)域中synchronized關(guān)鍵字一直都是元老級(jí)的角色,很久之前很多人都會(huì)稱它為 “重量級(jí)鎖” 。但是,在JavaSE 1.6之后進(jìn)行了主要包括為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的 偏向鎖 和 輕量級(jí)鎖 以及其它各種優(yōu)化之后變得在某些情況下并不是那么重了。synchronized的底層實(shí)現(xiàn)主要依靠 Lock-Free 的隊(duì)列,基本思路是 自旋后阻塞,競爭切換后繼續(xù)競爭鎖,稍微犧牲了公平性,但獲得了高吞吐量。在線程沖突較少的情況下,可以獲得和CAS類似的性能;而線程沖突嚴(yán)重的情況下,性能遠(yuǎn)高于CAS。
總結(jié)
以上是生活随笔為你收集整理的Java并发篇_乐观锁与悲观锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Hive-常用操作
- 下一篇: Mysql中的转义字符