Java中的CAS和ABA問題說明
1.CAS
1)CAS概念
CAS時Compare And Swap縮寫,即比較與交換是用于實現(xiàn)多線程同步的原子指令,它將內(nèi)存位置的內(nèi)容與給定值相比較,相同則修改內(nèi)存位置的值為新值,而整個操作是調用的UnSafe的compareAndSwapObject、compareAndSwapInt或者compareAndSwapLong完成的,而這些方法都是native修飾的本地方法,是一種系統(tǒng)原語系統(tǒng)支持的操作。
2)CAS產(chǎn)生的影響(無鎖執(zhí)行)
CAS是一種無鎖對象的原子操作,鎖分為樂觀鎖和悲觀鎖,樂觀派抱著幾乎不會發(fā)生修改同一資源的狀態(tài),任意操作同意對象資源,如果遇到修改同一資源的情況,資源不會修改成功,能夠保證資源的安全,而悲觀派會認為同一資源被錯誤修改后會造成不可挽回的局面,故自能有一個線程修改資源,這樣總會對系統(tǒng)性能產(chǎn)生一定的影響,拖慢自行速度,CAS即無鎖執(zhí)行者,被CAS修飾過的資源可以同時被多個線程修改依然能保證系統(tǒng)安全,無鎖不需要等待提高系統(tǒng)性能,jdk提供的CAS原理實現(xiàn)的并發(fā)類Automic系列運用及其原理介紹。
3)Automic并發(fā)類CAS原理代碼分析
首先介紹java的指針操作類UnSafe,Unsafe類是在sun.misc包下,不屬于Java標準。但是很多Java的基礎類庫,包括一些被廣泛使用的高性能開發(fā)庫都是基于Unsafe類開發(fā)的,因為UnSafe使Java像C語言一樣使其擁有操作內(nèi)存指針的能力,因為操作內(nèi)存指針容易出錯,故起名UnSafe不安全的類,因此Java官方并不建議使用的,但CAS原理就是UnSafe類中的compareAndSwapObject、compareAndSwapInt和compareAndSwapLong方法實現(xiàn)的,該方法需傳入四個參數(shù):第一個參數(shù)代表給定的對象,第二個參數(shù)代表給定對象再內(nèi)存中的偏移量,第三個參數(shù)標識對象的期望值,第四個參數(shù)標識要修改的值,并發(fā)保重的Automic系列的原子操作類都是使用UnSafe類實現(xiàn)的。
UnSafe源碼如下:
/** * 第一個參數(shù)var1代表給定對象,第二個參數(shù)var2代表var1對象在內(nèi)存中的偏移量,第三個參數(shù)var3為期望修改* 的對象舊值,第四個參數(shù)var4代表要修改的值或著說是修改后的值。 **/ public final native boolean compareAndSwapObject(Object var1, long var2, Object var3, Object var4); ? ? 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);
舉例AtomicInteger源碼實現(xiàn)原理:
AutomicInteger中的getAndSet實現(xiàn)原理解析:
? ? /** ? ? * 調用的UnSafe的getAndSetInt方法,給定值和偏移量和修改的值, ? ? * 獲取修改的值var5作為compareAndSwapInt的第三個參數(shù)用來和var1比較相同則執(zhí)行更新操作 ? ? * while循環(huán)知道操作成功。 ? ? *public final int getAndSetInt(Object var1, long var2, int var4) { ? ? * ? int var5; ? ? * ? do { ? ? * ? ? ? var5 = this.getIntVolatile(var1, var2); ? ? * ? } while(!this.compareAndSwapInt(var1, var2, var5, var4)); ? ? * ? ? * ? ?return var5; ? ? *} ? ? **/ ? ? public final int getAndSet(int newValue) { ? ? ? ? return unsafe.getAndSetInt(this, valueOffset, newValue); ? ? }
package java.util.concurrent.atomic; import java.util.function.IntUnaryOperator; import java.util.function.IntBinaryOperator; import sun.misc.Unsafe; public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // 獲取UnSafe對象實例 private static final Unsafe unsafe = Unsafe.getUnsafe(); //對象在內(nèi)存中的偏移量 private static final long valueOffset; //初始化valueOffset static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } //對象屬性值 private volatile int value; public AtomicInteger(int initialValue) { value = initialValue; } public AtomicInteger() { } /** * 調用的UnSafe的getAndSetInt方法,給定值和偏移量和修改的值, * 獲取修改的值var5作為compareAndSwapInt的第三個參數(shù)用來和var1比較相同則執(zhí)行更新操作 * while循環(huán)知道操作成功。 *public final int getAndSetInt(Object var1, long var2, int var4) { * int var5; * do { * var5 = this.getIntVolatile(var1, var2); * } while(!this.compareAndSwapInt(var1, var2, var5, var4)); * * return var5; *} **/ public final int getAndSet(int newValue) { return unsafe.getAndSetInt(this, valueOffset, newValue); } //調用UnSafe的compareAndSwapInt方法保證CAS public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } //調用UnSafe的compareAndSwapInt方法保證CAS public final boolean weakCompareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } //調用UnSafe的getAndAddInt再調用UnSafe的getAndSetInt方法保證CAS public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } public final int getAndDecrement() { return unsafe.getAndAddInt(this, valueOffset, -1); } ......... }
4)CAS導致的ABA問題
操作對象,獲取對象后,執(zhí)行CAS操作前,被其他線程修改后,且又修改為原來的對象值,導致CAS忽略其他線程的修改,成功執(zhí)行CAS對象修改,這種情況就叫做ABA問題。
下圖所示:
解決辦法:
AtomicStampedReference類提供了解決辦法,在對象之中又添加了stamp時間戳屬性避免其他線程修改了多次并變回修改前的value值,但對比stamp不同便可知道對象是被修改過的,只有提供屬性值和stamp時間戳相等才能成功執(zhí)行CAS修改操作,里面包裹了一個鍵值對對象AtomicStampedReference.Pair<V> pair類型,pair中值為屬性值,value為stamp時間戳,在執(zhí)行CAS操作時需要提供原值的value和時間戳都相等的情況才能成功執(zhí)行CAS操作。
AtomicMarkableReference類提供了解決辦法,在對象之中又添加了stamp時間戳屬性避免其他線程修改了多次并變回修改前的value值,但對比stamp不同便可知道對象是被修改過的,只有提供屬性值和boolean類型的mark標記相等才能成功執(zhí)行CAS修改操作,里面包裹了一個鍵值對對象AtomicMarkableReference.Pair<V> pair類型,pair中值為屬性值,value為mark是否被修改的標記,在執(zhí)行CAS操作時需要提供原值的value和mark標記都相等的情況才能成功執(zhí)行CAS操作。
本文只介紹AtomicStampedReference類的源碼分析,AtomicMarkableReference類同AtomicStampedReference類原理一樣,
源碼如下:
package java.util.concurrent.atomic; public class AtomicStampedReference<V> { /** * 對象值時一個AtomicStampedReference內(nèi)置對象Pair,包裹了reference和stamp兩個屬性 */ private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair; /** * 初始化對象并初始化pair */ public AtomicStampedReference(V initialRef, int initialStamp) { pair = Pair.of(initialRef, initialStamp); } /** * 比較當前對象屬性值和輸入原始值為真,在比較當前對象的時間stamp與期望的stamp進行比較 * 如果也想等,就更新值和stamp * @param expectedReference 原始值 * @param newReference 新值 * @param expectedStamp 期望時間 * @param newStamp 新時間 * @return {@code true} if successful */ public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; //賦值當前對象 return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); } // Unsafe mechanics private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe(); private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class); private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); } static long objectFieldOffset(sun.misc.Unsafe UNSAFE, String field, Class<?> klazz) { try { return UNSAFE.objectFieldOffset(klazz.getDeclaredField(field)); } catch (NoSuchFieldException e) { // Convert Exception to corresponding Error NoSuchFieldError error = new NoSuchFieldError(field); error.initCause(e); throw error; } } }
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
Java創(chuàng)建多線程的幾種方式實現(xiàn)
這篇文章主要介紹了Java創(chuàng)建多線程的幾種方式實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-10-10詳解Spring?Boot中@PostConstruct的使用示例代碼
在Java中,@PostConstruct是一個注解,通常用于標記一個方法,它表示該方法在類實例化之后(通過構造函數(shù)創(chuàng)建對象之后)立即執(zhí)行,這篇文章主要介紹了詳解Spring?Boot中@PostConstruct的使用,需要的朋友可以參考下2023-09-09Java聊天室之實現(xiàn)接收和發(fā)送Socket
這篇文章主要為大家詳細介紹了Java簡易聊天室之實現(xiàn)接收和發(fā)送Socket功能,文中的示例代碼講解詳細,具有一定的借鑒價值,需要的可以了解一下2022-10-10java并發(fā)編程專題(二)----如何創(chuàng)建并運行java線程
這篇文章主要介紹了java并發(fā)編程如何創(chuàng)建并運行java線程,文中講解非常詳細,示例代碼幫助大家更好的理解和學習,感興趣的朋友可以了解下2020-06-06