為了感謝支持我的朋友固歪!整理了一份Java高級架構(gòu)資料、Spring源碼分析胯努、Dubbo牢裳、Redis、Netty叶沛、zookeeper蒲讯、Spring cloud、分布式等資灰署。
本號專注Java源碼分析判帮。喜歡底層源碼的朋友可以來交流探討。交流群:818491202 驗(yàn)證:33
1. CAS是什么溉箕?
CAS全稱是Compare and Swap晦墙,即比較并交換,是通過原子指令來實(shí)現(xiàn)多線程的同步功能肴茄,將獲取存儲在內(nèi)存地址的原值和指定的內(nèi)存地址進(jìn)行比較晌畅,只有當(dāng)他們相等時,交換指定的預(yù)期值和內(nèi)存中的值寡痰,這個操作是原子操作抗楔,若不相等棋凳,則重新獲取存儲在內(nèi)存地址的原值。
2. CAS的流程
????? CAS是一種無鎖算法连躏,有3個關(guān)鍵操作數(shù)剩岳,內(nèi)存地址,舊的內(nèi)存中預(yù)期值入热,要更新的新值拍棕,當(dāng)內(nèi)存值和舊的內(nèi)存中預(yù)期值相等時,將內(nèi)存中的值更新為新值才顿。
3.樂觀鎖與悲觀鎖
CAS屬于樂觀鎖莫湘,樂觀鎖就是每次不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試郑气,直到成功為止幅垮。
synchronized是悲觀鎖,被一個線程拿到鎖之后尾组,其他線程必須等待該線程釋放鎖忙芒,性能較差
二、AtomicInteger代碼演示
在java中讳侨,a++不是原子操作呵萨,一個簡單的a++操作涉及到三個操作,獲取變量a的內(nèi)存值跨跨,將變量a+1潮峦,將新值寫入內(nèi)存,這里涉及到了兩次內(nèi)存訪問勇婴,如果在多線程環(huán)境下忱嘹,那么會出現(xiàn)并發(fā)安全問題。
AtomicInteger是一個原子操作類耕渴,內(nèi)部采用的就是CAS無鎖算法拘悦。這里我們分析一下它的內(nèi)部實(shí)現(xiàn)。
AtomicInteger atomicInteger = new AtomicInteger(0);atomicInteger.getAndSet(1);復(fù)制代碼
這里的靜態(tài)代碼塊AtomicInteger對象初始化之前就執(zhí)行橱脸,獲取AtomicInteger對象value字段相對AtomicInteger對象的”起始地址”的偏移量础米,Java對象在內(nèi)存中存儲的布局可以分為三塊區(qū)域:對象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)和對齊填充(Padding)添诉,”起始地址”的偏移量即是對象頭的偏移量屁桑。
static {? ? try {? ? ? ? valueOffset = unsafe.objectFieldOffset? ? ? ? ? ? (AtomicInteger.class.getDeclaredField("value"));? ? } catch (Exception ex) { throw new Error(ex); }}復(fù)制代碼
public final int getAndSet(int newValue) {returnunsafe.getAndSetInt(this, valueOffset, newValue);}復(fù)制代碼
每次通過內(nèi)存地址(var2)先從內(nèi)存中獲取內(nèi)存中原值(var5),再循環(huán)將內(nèi)存中的原值(var5)與給定內(nèi)存地址(var2)相比較栏赴,如果相等則更新指定預(yù)期值(var4)掏颊,如果不相等則再重試直到成功為止,最后返回舊的內(nèi)存原值var5。
//var1為AtomicInteger對象乌叶,var2為內(nèi)存地址值盆偿,var4為指定的預(yù)期值public final int getAndSetInt(Object var1, long var2, int var4) {? ? int var5;do{//unsafe.getIntVolatile調(diào)用本地方法獲取內(nèi)存中值? ? ? ? var5 = this.getIntVolatile(var1, var2);? ? }while(!this.compareAndSwapInt(var1, var2, var5, var4));returnvar5;}復(fù)制代碼
三、弊端
1. ABA問題
CAS在操作的時候會檢查變量的值是否被更改過准浴,如果沒有則更新值事扭,但是帶來一個問題,最開始的值是A乐横,接著變成B求橄,最后又變成了A。經(jīng)過檢查這個值確實(shí)沒有修改過葡公,因?yàn)樽詈蟮闹颠€是A罐农,但是實(shí)際上這個值確實(shí)已經(jīng)被修改過了。為了解決這個問題催什,在每次進(jìn)行操作的時候加上一個版本號涵亏,每次操作的就是兩個值,一個版本號和某個值蒲凶,A——>B——>A問題就變成了1A——>2B——>3A气筋。在jdk中提供了AtomicStampedReference類解決ABA問題,用Pair這個內(nèi)部類實(shí)現(xiàn)旋圆,包含兩個屬性宠默,分別代表版本號和引用,在compareAndSet中先對當(dāng)前引用進(jìn)行檢查灵巧,再對版本號標(biāo)志進(jìn)行檢查搀矫,只有全部相等才更新值。
2. 只能保證一個共享變量的原子操作
多個共享變量操作時刻肄,循環(huán)CAS就無法保證操作的原子性瓤球,這個時候就可以用鎖。從java1.5開始肄方,JDK提供了AtomicReference類來保證引用對象之間的原子性,就可以把多個變量放在一個對象里來進(jìn)行CAS操作蹬癌。
3. 循環(huán)時間長CPU開銷較大
在并發(fā)量比較高的情況下权她,如果許多線程反復(fù)嘗試更新某一個變量,卻又一直更新不成功逝薪,循環(huán)往復(fù)隅要,會給CPU帶來很大的壓力。
關(guān)注公眾號領(lǐng)資料
搜索公眾號【Java耕耘者】,回復(fù)【Java】董济,即可獲取大量優(yōu)質(zhì)電子書和一份Java高級架構(gòu)資料步清、Spring源碼分析、Dubbo、Redis廓啊、Netty欢搜、zookeeper、Spring cloud谴轮、分布式等視頻資料