Java中的CAS無鎖機(jī)制實(shí)現(xiàn)原理詳解
CAS(Compare And Swap) 比較和替換
無鎖機(jī)制,是樂觀鎖的一種實(shí)現(xiàn)
并發(fā)情況下保證對共享變量值更改的原子性 CAS是Java中Unsafe類里面的方法 底層通過調(diào)用C語言接口,再通過cup硬件指令保證原子性
實(shí)現(xiàn)算法
三個(gè)參數(shù)(V,E,N): V是要更新的變量,E是預(yù)期值,N是新值。當(dāng)V的值等于E值時(shí),將V的值設(shè)為N,若V值和E值不同,則說明已經(jīng)有其他線程做了更新,當(dāng)前線程方式自旋重來。
最后,CAS返回當(dāng)前V的真實(shí)值,CAS 一定要volatile變量配合,這樣才能保證每次拿到的變量是主內(nèi)存中最新的那個(gè)值
使用樂觀鎖思想,多個(gè)線程用CAS操作一個(gè)變量時(shí),只有一個(gè)成功且成功更新。
失敗的線程不會(huì)被掛起,僅是被告知失敗,允許再次嘗試,也允許失敗線程放棄操作?;谶@樣的原理,CAS操作即使沒有鎖,也可以發(fā)現(xiàn)其他線程對當(dāng)前線程的干擾,并進(jìn)行恰當(dāng)?shù)奶幚?/p>
與鎖相比,使用CAS會(huì)使程序看起來更加復(fù)雜一些,但由于其非阻塞性,對死鎖天生免疫,且線程間的相互影響也遠(yuǎn)遠(yuǎn)比基于鎖的方式要小,無鎖方式?jīng)]有鎖競爭,也沒有線程間頻繁調(diào)度的開銷。
因此,比基于鎖的方式擁有更優(yōu)越的性能
底層實(shí)現(xiàn)
通過硬件保證了比較更新的原子性和可見性,實(shí)現(xiàn)方式是基于硬件平臺(tái)的匯編指令,大部分的現(xiàn)代處理器都已經(jīng)支持原子化的CAS指令,在JDK 5.0以后,虛擬機(jī)便可以使用這個(gè)指令來實(shí)現(xiàn)并發(fā)操作和并發(fā)數(shù)據(jù)結(jié)構(gòu),CAS是cup的原子指令(cmpxchg),不會(huì)造成數(shù)據(jù)不一致,執(zhí)行cmpxchg指令時(shí),會(huì)判斷當(dāng)前系統(tǒng)是否是多核系統(tǒng),如果是就給總線加鎖,只會(huì)有一個(gè)線程給總線加鎖成功,加速成功之后會(huì)執(zhí)行cas操作,也就是cas的原子性實(shí)際上是cup實(shí)現(xiàn)的,比起synchronized,排他時(shí)間很短,多線程下性能高
優(yōu)點(diǎn)
(1) 高并發(fā)的情況下,比有鎖的程序擁有更好的性能,是輕量級(jí)鎖
(2) 它天生就是死鎖免疫的
(3) 線程不會(huì)阻塞(線程阻塞到喚醒運(yùn)行成本較高),一直處于用戶態(tài)
缺點(diǎn)
(1) 若一直獲取不到鎖死循環(huán),消耗cup資源,可能導(dǎo)致cup飆高(需要控制次數(shù))
(2) ABA問題:若內(nèi)存地址V初次讀取的值是A,在CAS等待期間它的值曾經(jīng)被改成了B,后來又被改回為A,那CAS操作就會(huì)誤認(rèn)為它從來沒有被改變過。解決辦法,加版本號(hào)屬性,每次值變化了給版本號(hào)加1,。ABA的問題對結(jié)果沒有影響,只會(huì)和CAS概念沖突了
(3) 不能保證代碼塊的原子性:CAS只保證一個(gè)變量的原子性,若更新多變量同時(shí)原子性要用synchronized
適用場景
CAS 適合簡單對象的操作,比如布爾值、整型值等;
典型的使用場景有兩個(gè)
(1) J.U.C里面Atomic的原子實(shí)現(xiàn),比如AtomicInteger,AtomicLong。
(2) 實(shí)現(xiàn)多線程對共享資源競爭的互斥性質(zhì),比如在AQS/ConcurrentHashMap/ConcurrentLinkedQueue等
通常和自旋鎖同時(shí)使用
代碼示例
有一個(gè)成員變量state,默認(rèn)值是0, 定義了一個(gè)方法doSomething(),判斷state是否為0 ,如果為0,就修改成1。
這個(gè)邏輯在多線程環(huán)境下,會(huì)存在原子性的問題,因?yàn)檫@里是一個(gè)典型的,Read - Write的操作。
一般會(huì)在doSomething()這個(gè)方法上加同步鎖來解決原子性問題, 但加同步鎖,會(huì)帶來性能上的損耗,
public class Example { private int state = 0; public void doSomething() { if (state == 0) { state = 1; } } }
使用CAS機(jī)制來進(jìn)行優(yōu)化
調(diào)用Unsafe類的objectFieldOffset()傳入類的變量得到變量在內(nèi)存中的偏移量
調(diào)用Unsafe類的compareAndSwapInt()方法進(jìn)行更新,傳入四個(gè)參數(shù):當(dāng)前對象實(shí)例/變量在內(nèi)存地址中的偏移量/預(yù)期值/更新值
比較變量內(nèi)存地址偏移量對應(yīng)的值和傳入的預(yù)期值是否相等,若相等,修改內(nèi)存地址中變量的值為要更新的值,否則,返回false
compareAndSwap()是native方法,底層實(shí)現(xiàn)中,在多核CPU環(huán)境下,會(huì)增加一個(gè)Lock指令對緩存或總線加鎖,從而保證比較并替換這兩個(gè)指令的原子性。
這里注意變量要加volatile
public class Example { private volatile int state = 0; private static final Unsafe UNSAFE = Unsafe.getUnsafe(); private static final long stateOffset; static { try { stateOffset = UNSAFE.objectFieldOffset(Example.class.getDeclaredField("state")); } catch (Exception ex){ throw new Error(ex); } } public void doSomething() { if (UNSAFE.compareAndSwapInt(this, stateOffset, 0 ,1)) { // TODO } } public int getState() { return state; } }
public static void main(String[] args) { Example example = new Example(); example.doSomething(); System.out.println(example.getState()); }
測試的時(shí)候會(huì)報(bào)錯(cuò)會(huì)在這行代碼 Unsafe.getUnsafe();
Caused by: java.lang.SecurityException: Unsafe
at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
at com.test.Example.<clinit>(Example.java:7)
... 1 more
查看Unsafe.getUnsafe()源碼
@CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }
如果不是systemClassLoader則會(huì)拋出SecurityException(“Unsafe”)異常,所以用戶編寫的程序使用不了unsafe實(shí)例。
怎么解決ABA問題,加一個(gè)版本號(hào) 單線程情況下
public static void main(String[] args) { Book javaBook = new Book(1,"javaBook"); // 參數(shù)1是版本號(hào) AtomicStampedReference<Book> stampedReference = new AtomicStampedReference<>(javaBook,1); System.out.println(stampedReference.getReference() + "\t" + stampedReference.getStamp()); Book mysqlBook = new Book(2,"mysqlBook"); boolean b; // 在判斷的時(shí)候不斷要判斷book,還要判斷版本號(hào),更新的時(shí)候更新book的值,也要更新版本號(hào)(這里是加1) b = stampedReference.compareAndSet(javaBook, mysqlBook, stampedReference.getStamp(), stampedReference.getStamp() + 1); System.out.println(b + "\t" + stampedReference.getReference() + "\t" + stampedReference.getStamp()); b = stampedReference.compareAndSet(mysqlBook, javaBook, stampedReference.getStamp(), stampedReference.getStamp() + 1); System.out.println(b+"\t"+stampedReference.getReference()+"\t"+stampedReference.getStamp()); }
對線程情亂下
public class ABADemo { static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1); public static void main(String[] args) { new Thread(() -> { int stamp = stampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + "\t" + "首次版本號(hào):" + stamp); // 暫停500毫秒,保證后面的t4線程初始化拿到的版本號(hào)和我一樣 try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 從100改到101 stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t"+"2次流水號(hào):"+stampedReference.getStamp()); // 從101改到100 stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t"+"3次流水號(hào):"+stampedReference.getStamp()); },"t3").start(); new Thread(() -> { int stamp = stampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+ "\t" + "首次版本號(hào):" + stamp); // 暫停1秒鐘線程,等待上面的t3線程,發(fā)生了ABA問題 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } boolean b = stampedReference.compareAndSet(100, 2022, stamp, stamp + 1); System.out.println(b+"\t"+stampedReference.getReference()+"\t"+stampedReference.getStamp()); },"t4").start(); } }
到此這篇關(guān)于Java中的CAS無鎖機(jī)制實(shí)現(xiàn)原理詳解的文章就介紹到這了,更多相關(guān)CAS無鎖機(jī)制原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud的@RefreshScope 注解你了解嗎
這篇文章主要介紹了Spring Cloud @RefreshScope 原理及使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-09-09JAVA 字符串加密、密碼加密實(shí)現(xiàn)方法
這篇文章主要介紹了JAVA 字符串加密、密碼加密實(shí)現(xiàn)方法的相關(guān)資料,需要的朋友可以參考下2016-10-10java算法題解LeetCode30包含min函數(shù)的棧實(shí)例
這篇文章主要為大家介紹了java算法題解LeetCode30包含min函數(shù)的棧實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01SpringBoot+Netty+WebSocket實(shí)現(xiàn)消息發(fā)送的示例代碼
這篇文章主要介紹了SpringBoot+Netty+WebSocket實(shí)現(xiàn)消息發(fā)送的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09使用Java和WebSocket實(shí)現(xiàn)網(wǎng)頁聊天室實(shí)例代碼
WebSocket是HTML5一種新的協(xié)議,它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信,這里就將使用WebSocket來開發(fā)網(wǎng)頁聊天室,對Java和WebSocket實(shí)現(xiàn)網(wǎng)頁聊天室的實(shí)例代碼感興趣的朋友一起學(xué)習(xí)吧2016-06-06SpringCloud Gateway網(wǎng)關(guān)功能介紹與使用
SpringCloud Gateway 是 Spring Cloud 的一個(gè)全新項(xiàng)目,它旨在為微服務(wù)架構(gòu)提供一種簡單有效的統(tǒng)一的 API 路由管理方式。這篇文章主要介紹了SpringCloud Gateway網(wǎng)關(guān)作用,需要的朋友可以參考下2022-12-12Spring boot 整合 Okhttp3 并封裝請求工具實(shí)例 詳解
OkHttp作為一款成熟、穩(wěn)定、易用的HTTP客戶端庫,擁有較高的性能和多樣化的功能,已被廣泛應(yīng)用于移動(dòng)應(yīng)用開發(fā)、Web服務(wù)端開發(fā)等領(lǐng)域,這篇文章主要介紹了Spring boot 整合 Okhttp3 并封裝請求工具,需要的朋友可以參考下2023-08-08