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