為了深入理解CAS的,我們從以下幾個維度去探究CAS义起,然后再去考慮為什么出現(xiàn)ABA問題。
1默终、CAS是什么?
2犁罩、為什么需要CAS算法齐蔽?
3、CAS解決的是什么問題床估?
4含滴、在jdk中有哪些是基于CAS實(shí)現(xiàn)的?
5丐巫、CAS底層實(shí)現(xiàn)原理是什么谈况?
6源哩、如何自己編寫一個類似CAS算法的實(shí)現(xiàn)【簡單版本】
7、CAS有哪些問題鸦做?
8励烦、CAS的ABA問題到底是什么意思?如何解決ABA問題泼诱?
CAS是什么坛掠?
CAS:一個算法,全稱為Compare And Swap治筒,比較且交換屉栓。在CAS中存在三個值,
V:內(nèi)存值
A:舊值或者叫預(yù)期值
B:更新值
當(dāng)且僅當(dāng)A==V (預(yù)期值==內(nèi)存值)耸袜,才會出現(xiàn)V=B友多,將更新值賦給內(nèi)存值,否則什么都不做堤框。
問題:為什么需要CAS算法域滥,到底CAS在做什么?三個值分別是什么意思蜈抓?
接下來我們通過一個個的測試用例去探究CAS:
測試用例1
在多線程情景下:
結(jié)果:
結(jié)論:在多線程下启绰,無法通過主線程改變其他線程中的內(nèi)容。
探究原因:
- java內(nèi)存模型(JMM:java memory mode)
-
多線程下如何處理共享數(shù)據(jù)沟使?
Java內(nèi)存模型的主要目標(biāo)是定義程序中各個變量的訪問規(guī)則委可,即在虛擬機(jī)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣底層細(xì)節(jié)。此處的變量與Java編程時所說的變量不一樣腊嗡,指包括了實(shí)例字段着倾、靜態(tài)字段和構(gòu)成數(shù)組對象的元素,但是不包括局部變量與方法參數(shù)燕少,后者是線程私有的卡者,不會被共享棺亭。
Java內(nèi)存模型中規(guī)定了所有的變量都存儲在主內(nèi)存中,每條線程還有自己的工作內(nèi)存嗽桩,線程的工作內(nèi)存中保存了該線程使用到的變量到主內(nèi)存副本拷貝凄敢,線程對變量的所有操作(讀取、賦值)都必須在工作內(nèi)存中進(jìn)行涝缝,而不能直接讀寫主內(nèi)存中的變量。不同線程之間無法直接訪問對方工作內(nèi)存中的變量罐氨,線程間變量值的傳遞均需要在主內(nèi)存來完成栅隐。
注意:這里的主內(nèi)存、工作內(nèi)存與Java內(nèi)存區(qū)域的Java堆谨究、棧胶哲、方法區(qū)不是同一層次內(nèi)存劃分潭辈,這兩者基本上沒有關(guān)系
測試用例2:解決可見性問題
解決方案:通過在共享變量上加入volatile
關(guān)鍵詞萎胰。volatile
關(guān)鍵詞只能保證內(nèi)存可見性,而不能保證原子性。 【ps榔组,關(guān)于volatile我們這次不做探究联逻,當(dāng)然這里除了這種方式之外還有其他解決辦法包归,我們也暫且先不做深入】
加入volatile測試代碼:
結(jié)果:
測試用例3:測試原子性問題
結(jié)果:
問題: 通過volatile不能保證我們原來數(shù)據(jù)的原子性問題换可,繼續(xù)往下深入
測試用例4:解決原子性問題
注意這里的標(biāo)注區(qū)域,通過AtomicInteger保證原子性
結(jié)果:
解決方案:通過使用java.util.concurrent.atomic.AtomicInteger幫助我們解決原子性問題译荞,而截至到這里我們才真正意義上準(zhǔn)備接觸CAS算法吞歼。這里對于AtomicXXX 不多做擴(kuò)展,我們回來繼續(xù)了解CAS本辐。
CAS解決了什么問題慎皱?
CAS幫助我們解決了數(shù)據(jù)一致性問題叶骨,或者是在上述的例子中忽刽,我們通過AtomicXXX類確保數(shù)據(jù)的原
子性。這里的數(shù)據(jù)一致性我們已經(jīng)在多線程情景下去模擬過了今膊。
并且在高并發(fā)CAS算法也能幫助我們很好的處理數(shù)據(jù)一致性問題斑唬,且它的實(shí)現(xiàn)是一種基于樂觀鎖的
策略實(shí)現(xiàn)黎泣。這樣要比直接使用Synchronized要來的更加高效抒倚,但是這里注意CAS本身的算法
實(shí)現(xiàn)我們說是不穩(wěn)定的由于這個問題也出現(xiàn)了我們說的ABA問題。
在jdk中有哪些是基于CAS實(shí)現(xiàn)的含蓉?
java.utit.concurrent.*;就是基于CAS算法實(shí)現(xiàn)的馅扣。沒有CAS就沒有這個包呆抑;
PS:深入擴(kuò)展一下,比如jvm對于對象分配時也會使用CAS算法厌殉。
java堆中的內(nèi)存分配【這里簡單擴(kuò)展】
我們都知道當(dāng)執(zhí)行new操作的時候,此時其實(shí)對象在堆中分配大小的空間是已經(jīng)確定了的器紧,因?yàn)槲覀?知道java是強(qiáng)類型語言铲汪。不同數(shù)據(jù)類型在堆中所占空間時確定的罐柳。
那么當(dāng)空間確定之后jvm到底是如何分配的呢?
內(nèi)存分配策略:
單線程分配策略
指針碰撞
一般適用于內(nèi)存絕對規(guī)整的[內(nèi)存是否規(guī)整取決于回收策略]齿梁,分配空間時只是通過指針向空閑內(nèi)存中移動創(chuàng)建對象大小的位置即可勺择。
空閑列表
一般適用于內(nèi)存非規(guī)整的省核,此時jvm內(nèi)部維護(hù)了一個列表[內(nèi)存列表]昆码。這個列表中記錄哪些空間時空閑的。分配空間時會在這個列表中查看笔刹,找尋合適的區(qū)域,然后進(jìn)行使用萌壳。
多線程分配策略
ps:給一個對象分配空間時袱瓮,其實(shí)不是原子操作的,需要一下幾步:
- 查找空閑列表(這里我們假設(shè)大多數(shù)情況下內(nèi)存都是非規(guī)整的以示一般性)
- 分配內(nèi)存
- 修改內(nèi)存列表 等操作
第一種多線程分配策略:CAS
jvm通過cas和失敗重試機(jī)制保證分配內(nèi)存之后的更新等操作的原子性绊起。
第二種多線程分配策略:TLAB
每個線程會在java堆中分配一個內(nèi)存[這個內(nèi)存比較小]虱歪,這個內(nèi)存稱之為本地線程分配緩沖區(qū)[TLAB],線程內(nèi)部如果需要分配內(nèi)存會首先在這個TALB上分配,避免線程沖突师枣,只有緩沖區(qū)用完之后才會在分配內(nèi)存時通過CAS操作分配更大的內(nèi)存空間萧落。jdk1.5之后默認(rèn)開啟TLAB,當(dāng)然可以手動關(guān)閉:-xx:+/-UseTLAB進(jìn)行配置陨倡。
我們正在這里先討論到這里兴革,下次繼續(xù)補(bǔ)齊剩余內(nèi)容爹脾,當(dāng)然在以上內(nèi)容中我們有拋出了一些新的問題:
- volatile關(guān)鍵詞的同步機(jī)制 有哪些優(yōu)缺點(diǎn)呢灵妨?
- java中的悲觀鎖除了Synchronized之外還有哪些?
- 樂觀鎖的實(shí)現(xiàn)機(jī)制是什么?
- 線程同步策略有哪些货抄?
以上內(nèi)容我們后續(xù)在繼續(xù)攻破V熳L傥!嘿嘿分别,奸計(jì)得逞耘斩,你以為你學(xué)會了一個東西桅咆,其實(shí)你是踩到坑里了,哈哈荚虚。請叫我坑王
。最后以上內(nèi)容中可能有不詳之處却邓,大家觀看時一定要存疑腊徙。
書上檬某、別人的不一定是對的恢恼,自己測試才能測出真理。
實(shí)踐是檢驗(yàn)真理的的唯一途徑
下篇內(nèi)容地址:http://www.reibang.com/p/fbab74f4fa74
版權(quán)聲明:本文為原創(chuàng)文章漓踢,未經(jīng)博主允許不責(zé)轉(zhuǎn)載喧半。
原文地址:http://www.reibang.com/p/15f17da87ba6
參考文檔: