Java CAS底層實現(xiàn)原理實例詳解
這篇文章主要介紹了Java CAS底層實現(xiàn)原理實例詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
一、CAS(compareAndSwap)的概念
CAS,全稱Compare And Swap(比較與交換),解決多線程并行情況下使用鎖造成性能損耗的一種機制。
CAS(V, A, B),V為內(nèi)存地址、A為預期原值,B為新值。如果內(nèi)存地址的值與預期原值相匹配,那么將該位置值更新為新值。否則,說明已經(jīng)被其他線程更新,處理器不做任何操作;無論哪種情況,它都會在 CAS 指令之前返回該位置的值。而我們可以使用自旋鎖,循環(huán)CAS,重新讀取該變量再嘗試再次修改該變量,也可以放棄操作。
二、CAS(compareAndSwap)的產(chǎn)生
為什么需要CAS機制呢?我們先從一個錯誤現(xiàn)象談起。我們經(jīng)常使用volatile關鍵字修飾某一個變量,表明這個變量是全局共享的一個變量,同時具有了可見性和有序性。但是卻沒有原子性。比如說一個常見的操作a++。這個操作其實可以細分成三個步驟:
(1)從內(nèi)存中讀取a
(2)對a進行加1操作
(3)將a的值重新寫入內(nèi)存中
在單線程狀態(tài)下這個操作沒有一點問題,但是在多線程中就會出現(xiàn)各種各樣的問題了。因為可能一個線程對a進行了加1操作,還沒來得及寫入內(nèi)存,其他的線程就讀取了舊值。造成了線程的不安全現(xiàn)象。
Volatile關鍵字可以保證線程間對于共享變量的可見性可有序性,可以防止CPU的指令重排序(DCL單例),但是無法保證操作的原子性,所以jdk1.5之后引入CAS利用CPU原語保證線程操作的院子性。
CAS操作由處理器提供支持,是一種原語。原語是操作系統(tǒng)或計算機網(wǎng)絡用語范疇。是由若干條指令組成的,用于完成一定功能的一個過程,具有不可分割性,即原語的執(zhí)行必須是連續(xù)的,在執(zhí)行過程中不允許被中斷。如 Intel 處理器,比較并交換通過指令的 cmpxchg 系列實現(xiàn)。
三、CAS(compareAndSwap)的原理探究
CAS的實現(xiàn)主要在JUC中的atomic包,我們以AtomicInteger類為例:
通過代碼追溯,可以看出JAVA中的CAS操作都是通過sun包下Unsafe類實現(xiàn),而Unsafe類中的方法都是native方法,由JVM本地實現(xiàn),所以最終的實現(xiàn)是基于C、C++在操作系統(tǒng)之上操作
Unsafe類,在sun.misc包下,不屬于Java標準。Unsafe類提供一系列增加Java語言能力的操作,如內(nèi)存管理、操作類/對象/變量、多線程同步等
//var1為CAS操作的對象,offset為var1某個屬性的地址偏移值,expected為期望值,var2為要設置的值,利用JNI來完成CPU指令的操作 public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6); public native Object getObjectVolatile(Object var1, long var2); public native void putObjectVolatile(Object var1, long var2, Object var4);
Hotspot源碼中關于unsafe的實現(xiàn)hotspot\src\share\vm\prims\unsafe.cpp UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) UnsafeWrapper("Unsafe_CompareAndSwapInt"); oop p = JNIHandles::resolve(obj);根據(jù)偏移量,計算value的地址。這里的offset就是 AtomaicInteger中的valueOffset jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); return (jint)(Atomic::cmpxchg(x, addr, e)) == e; UNSAFE_END\hotspot\src\share\vm\runtime\atomic.cppunsigned Atomic::cmpxchg(unsigned int exchange_value, volatile unsigned int* dest, unsigned int compare_value) { assert(sizeof(unsigned int) == sizeof(jint), "more work to do"); return (unsigned int)Atomic::cmpxchg((jint)exchange_value, (volatile jint*)dest, (jint)compare_value); }根據(jù)操作系統(tǒng)類型調(diào)用不同平臺下的重載函數(shù),這個在預編譯期間編譯器會決定調(diào)用哪個平臺下的重載
可以看到調(diào)用了“Atomic::cmpxchg”方法,“Atomic::cmpxchg”方法在linux_x86和windows_x86的實現(xiàn)如下
linux_x86底層實現(xiàn)\hotspot\src\os_cpu\linux_x86\vm\atomic_linux_x86.inline.hpp inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { int mp = os::is_MP(); __asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)" : "=a" (exchange_value) : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp) : "cc", "memory"); return exchange_value; }
windows_x86底層實現(xiàn) hotspot\src\os_cpu\windows_x86\vmatomic_linux_x86.inline.hpp inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { // alternative for InterlockedCompareExchange int mp = os::is_MP(); __asm { mov edx, dest mov ecx, exchange_value mov eax, compare_value LOCK_IF_MP(mp) cmpxchg dword ptr [edx], ecx } }
總結(jié):根據(jù)資料查詢,其實CAS底層實現(xiàn)根據(jù)不同的操作系統(tǒng)會有不同重載,CAS的實現(xiàn)離不開處理器的支持。
核心代碼就是一條帶lock 前綴的 cmpxchg 指令,即lock cmpxchg dword ptr [edx], ecx
Atomic::cmpxchg方法解析:
mp是“os::is_MP()”的返回結(jié)果,“os::is_MP()”是一個內(nèi)聯(lián)函數(shù),用來判斷當前系統(tǒng)是否為多處理器。
如果當前系統(tǒng)是多處理器,該函數(shù)返回1。
否則,返回0。
LOCK_IF_MP(mp)會根據(jù)mp的值來決定是否為cmpxchg指令添加lock前綴。
如果通過mp判斷當前系統(tǒng)是多處理器(即mp值為1),則為cmpxchg指令添加lock前綴。
否則,不加lock前綴。
這是一種優(yōu)化手段,認為單處理器的環(huán)境沒有必要添加lock前綴,只有在多核情況下才會添加lock前綴,因為lock會導致性能下降。cmpxchg是匯編指令,作用是比較并交換操作數(shù)。
四、CAS機制的優(yōu)缺點
4.1 優(yōu)點
cas是一種樂觀鎖,而且是一種非阻塞的輕量級的樂觀鎖,什么是非阻塞式的呢?其實就是一個線程想要獲得鎖,對方會給一個回應表示這個鎖能不能獲得。在資源競爭不激烈的情況下性能高,相比synchronized重量鎖,synchronized會進行比較復雜的加鎖,解鎖和喚醒操作。
4.2 缺點
1)循環(huán)時間長開銷大,占用CPU資源
2)只能保證一個共享變量的原子操作
3)ABA問題
4.3 解決ABA問題
1)添加版本號
2)AtomicStampedReference
java并發(fā)包為了解決這個問題,提供了一個帶有標記的原子引用類“AtomicStampedReference”,它可以通過控制變量值的版本來保證CAS的正確性。因此,在使用CAS前要考慮清楚“ABA”問題是否會影響程序并發(fā)的正確性,如果需要解決ABA問題,改用傳統(tǒng)的互斥同步可能會比原子類更高效。
五、CAS使用的時機
5.1 線程數(shù)較少、等待時間短可以采用自旋鎖進行CAS嘗試拿鎖,較于synchronized高效
5.2 線程數(shù)較大、等待時間長,不建議使用自旋鎖,占用CPU較高
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
java用接口、多態(tài)、繼承、類計算三角形和矩形周長及面積的方法
這篇文章主要介紹了java用接口、多態(tài)、繼承、類計算三角形和矩形周長及面積的方法,涉及java面向?qū)ο笾蓄?、接口、多態(tài)等的使用技巧,需要的朋友可以參考下2015-05-05Java數(shù)據(jù)類型Integer與int的區(qū)別詳細解析
這篇文章主要介紹了Java數(shù)據(jù)類型Integer與int的區(qū)別詳細解析,Ingeter是int的包裝類,int的初值為0,Ingeter的初值為null,int和integer(無論new否)比,都為true,因為會把Integer自動拆箱為int再去比,需要的朋友可以參考下2023-12-12springboot項目編譯提示無效的源發(fā)行版17解決辦法
這篇文章主要給大家介紹了關于springboot項目編譯提示無效的源發(fā)行版17解決辦法,這個錯誤意味著你的Spring Boot項目正在使用Java 17這個版本,但是你的項目中未配置正確的Java版本,需要的朋友可以參考下2023-06-06解決java.lang.NoClassDefFoundError錯誤的問題
在Java開發(fā)過程中,NoClassDefFoundError是一個常見的運行時錯誤,是由于JVM在運行時找不到已編譯的類文件導致的,本文就來介紹一下如何解決,具有一定的參考價值,感興趣的可以了解一下2024-09-09Spring之spring-context-indexer依賴詳解
這篇文章主要介紹了Spring之spring-context-indexer依賴詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11