Java中的CAS鎖機(jī)制(無(wú)鎖、自旋鎖、樂(lè)觀鎖、輕量級(jí)鎖)詳解
什么是CAS機(jī)制(compare and swap)
CAS算法的作用:解決多線程條件下使用鎖造成性能損耗問(wèn)題的算法,保證了原子性,這個(gè)原子操作是由CPU來(lái)完成的
CAS的原理:CAS算法有三個(gè)操作數(shù),通過(guò)內(nèi)存中的值(V)、預(yù)期原始值(A)、修改后的新值。
(1)如果內(nèi)存中的值和預(yù)期原始值相等, 就將修改后的新值保存到內(nèi)存中。
(2)如果內(nèi)存中的值和預(yù)期原始值不相等,說(shuō)明共享數(shù)據(jù)已經(jīng)被修改,放棄已經(jīng)所做的操作,然后重新執(zhí)行剛才的操作,直到重試成功。
注意:
(1)預(yù)期原始值(A)是從偏移位置讀取到三級(jí)緩存中讓CPU處理的值,修改后的新值是預(yù)期原始值經(jīng)CPU處理暫時(shí)存儲(chǔ)在CPU的三級(jí)緩存中的值,而內(nèi)存指定偏移位置中的原始值。
(2)比較從指定偏移位置讀取到緩存的值與指定內(nèi)存偏移位置的值是否相等,如果相等則修改指定內(nèi)存偏移位置的值,這個(gè)操作是操作系統(tǒng)底層匯編的一個(gè)原子指令實(shí)現(xiàn)的,保證了原子性
- JVM中CAS是通過(guò)UnSafe類來(lái)調(diào)用操作系統(tǒng)底層的CAS指令實(shí)現(xiàn)。
- CAS基于樂(lè)觀鎖思想來(lái)設(shè)計(jì)的,其不會(huì)引發(fā)阻塞,synchronize會(huì)導(dǎo)致阻塞。
原子類
java.util.concurrent.atomic包下的原子類都使用了CAS算法。而java.util.concurrent中的大多數(shù)類的實(shí)現(xiàn)都直接或間接的使用了這些原子類。 Unsafe類使Java擁有了類似C語(yǔ)言指針操作內(nèi)存空間的能力,同時(shí)也帶來(lái)了指針的安全問(wèn)題。
AtomicInteger原子類
AtomicInteger等原子類沒(méi)有使用synchronized鎖,而是通過(guò)volatile和CAS(Compare And Swap)解決資源的線程安全問(wèn)題。
(1)volatile保證了可見(jiàn)性和有序性
(2)CAS保證了原子性,而且是無(wú)鎖操作,提高了并發(fā)效率。
//創(chuàng)建Unsafe類的實(shí)例 private static final Unsafe unsafe = Unsafe.getUnsafe(); //成員變量value是在內(nèi)存地址中距離當(dāng)前對(duì)象首地址的偏移量, 具體賦值是在下面的靜態(tài)代碼塊中中進(jìn)行的 private static final long valueOffset; static { try { //類加載的時(shí)候,在靜態(tài)代碼塊中獲取變量value的偏移量 valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } // 當(dāng)前AtomicInteger原子類的value值 private volatile int value; //類似于i++操作 public final int getAndIncrement() { //this代表當(dāng)前AtomicInteger類型的對(duì)象,valueOffset表示value成員變量的偏移量 return unsafe.getAndAddInt(this, valueOffset, 1); } ================================上方為AtomicInteger類中的方法,下方為Unsafe類中的方法========================================================= //此方法的作用:獲取內(nèi)存地址為原子對(duì)象首地址+原子對(duì)象value屬性地址偏移量, 并將該變量值加上delta public final int getAndAddInt(Object obj, long offset, int delta) { int v; do { //通過(guò)對(duì)象和偏移量獲取變量值作為期望值,在修改該內(nèi)存偏移位置的值時(shí)與原始進(jìn)行比較 //此方法中采用volatile的底層原理,保證了內(nèi)存可見(jiàn)性,所有線程都從內(nèi)存中獲取變量vlaue的值,所有線程看到的值一致。 v= this.getIntVolatile(obj, offset); /* while中的compareAndSwapInt()方法嘗試修改v的值,具體地, 該方法也會(huì)通過(guò)obj和offset獲取變量的值 如果這個(gè)值和v不一樣, 說(shuō)明其他線程修改了obj+offset地址處的值, 此時(shí)compareAndSwapInt()返回false, 繼續(xù)循環(huán) 如果這個(gè)值和v一樣, 說(shuō)明沒(méi)有其他線程修改obj+offset地址處的值, 此時(shí)可以將obj+offset地址處的值改為v+delta, compareAndSwapInt()返回true, 退出循環(huán) Unsafe類中的compareAndSwapInt()方法是原子操作, 所以compareAndSwapInt()修改obj+offset地址處的值的時(shí)候不會(huì)被其他線程中斷 */ } while(!this.compareAndSwapInt(obj, offset, v, v + delta)); return v; }
操作步驟:
(1)獲取AtomicInteger對(duì)象首地址指定偏移量位置上的值,作為期望值。
(2)取出獲取AtomicInteger對(duì)象偏移量上的值,判斷與期望值是否相等,相等就修改AtomicInteger在內(nèi)存偏移量上的值,不相等就返回false,重新執(zhí)行第一步操作,重新獲取內(nèi)存指定偏移量位置的值。
(3) 如果相等,則修改值并返回true。
注意:從1、2步可以看CAS機(jī)制實(shí)現(xiàn)的鎖是自旋鎖,如果線程一直無(wú)法獲取到鎖,則一直自旋,不會(huì)阻塞
CAS和syncronized的比較
CAS線程不會(huì)阻塞,線程一致自旋 syncronized會(huì)阻塞線程,會(huì)進(jìn)行線程的上下文切換,會(huì)由用戶態(tài)切換到內(nèi)核態(tài),切換前需要保存用戶態(tài)的上下文,而內(nèi)核態(tài)恢復(fù)到用戶態(tài),又需要恢復(fù)保存的上下文,非常消耗資源。
CAS的缺點(diǎn)
(1)ABA問(wèn)題 如果一個(gè)線程t1正修改共享變量的值A(chǔ),但還沒(méi)修改,此時(shí)另一個(gè)線程t2獲取到CPU時(shí)間片,將共享變量的值A(chǔ)修改為B,然后又修改為A,此時(shí)線程t1檢查發(fā)現(xiàn)共享變量的值沒(méi)有發(fā)生變化,但是實(shí)際上卻變化了。
解決辦法: 使用版本號(hào),在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加1,那么A-B-A 就會(huì)變成1A-2B-3A。從Java1.5開(kāi)始JUC包里提供了一個(gè)類AtomicStampedReference來(lái)解決ABA問(wèn)題。AtomicStampedReference類的compareAndSet方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前版本號(hào)是否等于預(yù)期版本號(hào),如果全部相等,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。
(2)循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)會(huì)比較大:自旋重試時(shí)間,會(huì)給CPU帶來(lái)非常大的執(zhí)行開(kāi)銷(xiāo)
(3)只能保證一個(gè)共享變量的原子操作,不能保證同時(shí)對(duì)多個(gè)變量的原子性操作 解決辦法: 從Java1.5開(kāi)始JDK提供了AtomicReference類來(lái)保證引用對(duì)象之間的原子性,你可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行CAS操作
CAS使用注意事項(xiàng)
(1)CAS需要和volatile配合使用
CAS只能保證變量的原子性,不能保證變量的內(nèi)存可見(jiàn)性。CAS獲取共享變量的值時(shí),需要和volatile配合使用,來(lái)保證共享變量的可見(jiàn)性
(2)CAS適用于并發(fā)量不高、多核CPU的情況
CPU多核情況下可以同時(shí)執(zhí)行,如果不合適就失敗。而并發(fā)量過(guò)高,會(huì)導(dǎo)致自旋重試耗費(fèi)大量的CPU資源
到此這篇關(guān)于Java中的CAS鎖機(jī)制(無(wú)鎖、自旋鎖、樂(lè)觀鎖、輕量級(jí)鎖)詳解的文章就介紹到這了,更多相關(guān)CAS鎖機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java使用單向鏈表解決數(shù)據(jù)存儲(chǔ)自定義排序問(wèn)題
本文主要介紹了java使用單向鏈表解決數(shù)據(jù)存儲(chǔ)自定義排序問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Java使用modbus-master-tcp實(shí)現(xiàn)modbus tcp通訊
這篇文章主要為大家詳細(xì)介紹了另外一種Java語(yǔ)言的modbux tcp通訊方案,那就是modbus-master-tcp,文中的示例代碼講解詳細(xì),需要的可以了解下2023-12-12使用Java實(shí)現(xiàn)讀取手機(jī)文件名稱
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)讀取手機(jī)文件名稱,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03SpringCloud?Function?SpEL注入漏洞分析及環(huán)境搭建
SpringCloud 是一套分布式系統(tǒng)的解決方案,常見(jiàn)的還有阿里巴巴的Dubbo,F(xiàn)ass的底層實(shí)現(xiàn)就是函數(shù)式編程,SpringCloud Function 就是Spring提供的分布式函數(shù)式編程組件,下面給大家介紹下SpringCloud?Function?SpEL注入漏洞分析,感興趣的朋友一起看看吧2022-04-04詳解Spring Boot 使用Spring security 集成CAS
本篇文章主要介紹了詳解Spring Boot 使用Spring security 集成CAS,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05Java中replace、replaceAll和replaceFirst函數(shù)的用法小結(jié)
相信會(huì)java的同學(xué)估計(jì)都用過(guò)replace、replaceAll、replaceFirst這三個(gè)函數(shù),可是,我們真的懂他們嗎?下面通過(guò)這篇文章大家再來(lái)好好學(xué)習(xí)學(xué)習(xí)下這幾個(gè)函數(shù)。2016-09-09解析Spring中@Controller@Service等線程安全問(wèn)題
這篇文章主要為大家介紹解析了Spring中@Controller@Service等線程的安全問(wèn)題,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03