java 鎖(二):樂觀鎖VS悲觀鎖

概念

對于同一個數(shù)據(jù)的并發(fā)操作,悲觀鎖認為自己在使用數(shù)據(jù)的時候一定有別的線程來修改數(shù)據(jù)戳稽,因此在獲取數(shù)據(jù)的時候會先加鎖馆蠕,確保數(shù)據(jù)不會被別的線程修改期升。Java中,synchronized關(guān)鍵字和Lock的實現(xiàn)類都是悲觀鎖互躬。

而樂觀鎖認為自己在使用數(shù)據(jù)時不會有別的線程修改數(shù)據(jù)播赁,所以不會添加鎖,只是在更新數(shù)據(jù)的時候去判斷之前有沒有別的線程更新了這個數(shù)據(jù)吼渡。如果這個數(shù)據(jù)沒有被更新容为,當前線程將自己修改的數(shù)據(jù)成功寫入。如果數(shù)據(jù)已經(jīng)被其他線程更新寺酪,則根據(jù)不同的實現(xiàn)方式執(zhí)行不同的操作(例如報錯或者自動重試)坎背。

樂觀鎖在java中是通過無鎖編程來實現(xiàn)的,最長采用的是CAS算法寄雀,java原子類中的遞增操作就通過CAS自旋實現(xiàn)的得滤。

使用場景:

悲觀鎖適合寫操作多的場景,先加鎖可以保證寫操作時數(shù)據(jù)正確盒犹。

樂觀鎖適合讀操作多的場景懂更,不加鎖的特點能夠使其讀操作的性能大幅提升。

調(diào)用方式:

悲觀鎖的調(diào)用方式

public synchronized void testMethod() {
      //操作同步資源 
 }

Private ReentrantLock lock = new ReentrantLock(); //需要保證多個線程使用的是同一個鎖

Public void modifyPublicResource() {
    lock.lock();
    //操作同步資源
    lock.unlock();
}

樂觀鎖的調(diào)用方式

private AtomicInteger atomicInteger = new AtomicInteger();   需要保證多個線程使用的是同一個

AtomicInteger.incrementAndGet();  執(zhí)行自增

通過調(diào)用方式示例急膀,我們可以發(fā)現(xiàn)悲觀鎖基本都是在顯式的鎖定之后再操作同步資源沮协,而樂觀鎖直接去操作同步資源。那么卓嫂,為何樂觀鎖能夠做到不鎖定同步資源也可以正確的實現(xiàn)線程同步呢慷暂?我們通過介紹樂觀鎖的主要實現(xiàn)方式“CAS”的技術(shù)原理為大家解惑。

CAS全稱Compare And Swap(比較與互換)晨雳,是一種無鎖算法行瑞。在不適用鎖(沒有線程被阻塞)的情況下實現(xiàn)多線程之間的變量同步。Java.util.concurrent包中的原子類就是通過CAS實現(xiàn)了樂觀鎖悍募。

CAS算法涉及到三個操作數(shù):

需要讀寫的內(nèi)存值V蘑辑。

進行比較的值A(chǔ)。

要寫入的新值B坠宴。

當且僅當V的值等于A時洋魂,CAS通過原子方式用新值B來更新V的值(“比較 + 更新”整體是一個原子操作),否則不會執(zhí)行任何操作喜鼓。一般情況下副砍,“更新”是一個不斷重試的操作。

之前提到j(luò)ava.util.concurrent包中的原子類庄岖,就是通過CAS來實現(xiàn)了樂觀鎖豁翎,那么我們進入原子類AtomicInteger的源碼,看一下AtomicInteger的定義:

image.png

根據(jù)定義我們可以看出各屬性的作用:

Unsafe:獲取并操作內(nèi)存是數(shù)據(jù)隅忿。

ValueOffset:存儲value在AtomicInteger中的偏移量心剥。

Value:存儲AtomicInteger的int值邦尊,該屬性需要借助volatile關(guān)鍵字保證其在線程間是可見的。

接下來优烧,我們查看AtomicInteger的自增函數(shù)incrementAndGet()的源碼時蝉揍,發(fā)現(xiàn)自增函數(shù)底層調(diào)用的是unsafe.getAndAddInt()。但是由于JDK本身只有Unsafe.class畦娄,只通過class文件中的參數(shù)名又沾,并不能很好的了解方法的作用,所以我們通過OpenJDK8來查看Unsafe的源碼:

image.png
image.png

根據(jù)OpenJDK8的源碼我們可以看出熙卡,getAndAddInt()循環(huán)獲取給定對象o中的偏移量處的值v杖刷,然后判斷內(nèi)存值是否等于v。如果相等則將內(nèi)存值設(shè)為v + delta驳癌,否則返回false滑燃;繼續(xù)循環(huán)進行重試,知道設(shè)置成功才能退出循環(huán)喂柒,并且將舊值返回不瓶。整個“比較+更新”操作封裝在compareAndSwapInt()中禾嫉,在JNI里是借助于一個CPU指令完成的灾杰,屬于原子操作,可以保證多個線程都能夠看到同一個變量的修改值熙参。

后續(xù)JDK通過CPU的cmpxchg指令艳吠,去比較寄存器中的A和內(nèi)存中的值V。如果相等孽椰,就把要寫入的新值B存入內(nèi)存中昭娩。如果不相等,就將內(nèi)存值V賦值給寄存器中的值A(chǔ)黍匾。然后通過Java代碼中的while循環(huán)再次調(diào)用cmpxchg指令進行重試栏渺,直到設(shè)置成功為止。

CAS存在的三大問題:

  1. ABA問題锐涯。CAS需要在操作值的時候檢查內(nèi)存值是否發(fā)生變化磕诊,沒有發(fā)生變化才回更新內(nèi)存值。但是如果內(nèi)存值原來是A纹腌,后來變成了B霎终,然后又變成了A,呢么CAS進行檢查時會發(fā)現(xiàn)值沒有發(fā)生變化升薯,但是實際上是有變化的莱褒。ABA問題的解決思路就是在變量前面添加版本號,每次變量更新的時候都把版本號加一涎劈,這樣變化過程就從“A-B-A”變成了“1A-2B-3A”广凸。

JDK從1.5開始提供了AtomicStampedReference類來解決ABA問題阅茶,具體操作封裝在 compareAndSet()中。compareAndSet()首先檢查當前引用和當前標志與預(yù)期引用標志是 否相等谅海,如果都相等目派,則以原子方式將引用值和標志的值設(shè)置為給定的更新值。

  1. 循環(huán)時間長開銷大胁赢。CAS操作如果長時間不成功企蹭,會導(dǎo)致其一直自旋,給CPU帶來非常大的開銷智末。

  2. 只能保證一個共享變量的原子操作谅摄。對一個共享變量執(zhí)行操作時,CAS能夠保證原子操作系馆,但是對多個共享變量操作時送漠,CAS是無法保證操作的原子性的。

Java從1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性由蘑,可以把多個變量放在一個對象里來進行CAS操作闽寡。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市尼酿,隨后出現(xiàn)的幾起案子爷狈,更是在濱河造成了極大的恐慌,老刑警劉巖裳擎,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涎永,死亡現(xiàn)場離奇詭異,居然都是意外死亡鹿响,警方通過查閱死者的電腦和手機羡微,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惶我,“玉大人妈倔,你說我怎么就攤上這事〕窆保” “怎么了盯蝴?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長恃轩。 經(jīng)常有香客問我结洼,道長,這世上最難降的妖魔是什么叉跛? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任松忍,我火速辦了婚禮,結(jié)果婚禮上筷厘,老公的妹妹穿的比我還像新娘鸣峭。我一直安慰自己宏所,他們只是感情好,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布摊溶。 她就那樣靜靜地躺著爬骤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪莫换。 梳的紋絲不亂的頭發(fā)上霞玄,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天,我揣著相機與錄音拉岁,去河邊找鬼坷剧。 笑死,一個胖子當著我的面吹牛喊暖,可吹牛的內(nèi)容都是我干的惫企。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼陵叽,長吁一口氣:“原來是場噩夢啊……” “哼狞尔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起巩掺,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤偏序,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锌半,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體禽车,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年刊殉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片州胳。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡记焊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出栓撞,到底是詐尸還是另有隱情遍膜,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布瓤湘,位于F島的核電站瓢颅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏弛说。R本人自食惡果不足惜挽懦,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望木人。 院中可真熱鬧信柿,春花似錦冀偶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至形病,卻和暖如春客年,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背漠吻。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工搀罢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侥猩。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓榔至,卻偏偏與公主長得像,于是被迫代替她去往敵國和親欺劳。 傳聞我的和親對象是個殘疾皇子唧取,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

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