面试必问的 CAS ,要多了解
轉載自?面試必問的 CAS ,要多了解
前言
CAS(Compare and Swap),即比較并替換,實現并發算法時常用到的一種技術,Doug lea大神在java同步器中大量使用了CAS技術,鬼斧神工的實現了多線程執行的安全性。
CAS的思想很簡單:三個參數,一個當前內存值V、舊的預期值A、即將更新的值B,當且僅當預期值A和內存值V相同時,將內存值修改為B并返回true,否則什么都不做,并返回false。
問題
一個n++的問題。
| 12345678 | publicclass Case {????publicvolatile int n;????publicvoid add() {????????n++;????}} |
通過javap -verbose Case看看add方法的字節碼指令
| 1234567891011 | publicvoid add();????flags: ACC_PUBLIC????Code:??????stack=3, locals=1, args_size=1?????????0: aload_0?????? ?????????1: dup?????????? ?????????2: getfield????? #2?????????????????// Field n:I?????????5: iconst_1????? ?????????6: iadd????????? ?????????7: putfield????? #2?????????????????// Field n:I????????10:return |
n++被拆分成了幾個指令:
通過volatile修飾的變量可以保證線程之間的可見性,但并不能保證這3個指令的原子執行,在多線程并發執行下,無法做到線程安全,得到正確的結果,那么應該如何解決呢?
如何解決
在add方法加上synchronized修飾解決。
| 12345678 | publicclass Case {????publicvolatile int n;????publicsynchronized void add() {????????n++;????}} |
這個方案當然可行,但是性能上差了點,還有其它方案么?
再來看一段代碼
| 12345678 | publicint a = 1;publicboolean compareAndSwapInt(intb) {????if(a == 1) {????????a = b;????????returntrue;????}????returnfalse;} |
如果這段代碼在并發下執行,會發生什么?
假設線程1和線程2都過了a==1的檢測,都準備執行對a進行賦值,結果就是兩個線程同時修改了變量a,顯然這種結果是無法符合預期的,無法確定a的最終值。
解決方法也同樣暴力,在compareAndSwapInt方法加鎖同步,變成一個原子操作,同一時刻只有一個線程才能修改變量a。
除了低性能的加鎖方案,我們還可以使用JDK自帶的CAS方案,在CAS中,比較和替換是一組原子操作,不會被外部打斷,且在性能上更占有優勢。
下面以AtomicInteger的實現為例,分析一下CAS是如何實現的。
| 123456789101112131415 | publicclass AtomicInteger extendsNumber implementsjava.io.Serializable {????// setup to use Unsafe.compareAndSwapInt for updates????privatestatic final Unsafe unsafe = Unsafe.getUnsafe();????privatestatic final long valueOffset;????static{????????try{????????????valueOffset = unsafe.objectFieldOffset????????????????(AtomicInteger.class.getDeclaredField("value"));????????}catch(Exception ex) { thrownew Error(ex); }????}????privatevolatile int value;????publicfinal int get() {returnvalue;}} |
看看AtomicInteger如何實現并發下的累加操作:
| 123456789101112 | publicfinal int getAndAdd(intdelta) {??? ????returnunsafe.getAndAddInt(this, valueOffset, delta);}//unsafe.getAndAddIntpublicfinal int getAndAddInt(Object var1, longvar2, intvar4) {????intvar5;????do{????????var5 = this.getIntVolatile(var1, var2);????}while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));????returnvar5;} |
假設線程A和線程B同時執行getAndAdd操作(分別跑在不同CPU上):
整個過程中,利用CAS保證了對于value的修改的并發安全,繼續深入看看Unsafe類中的compareAndSwapInt方法實現。
| 1 | publicfinal native boolean compareAndSwapInt(Object paramObject, longparamLong, intparamInt1, intparamInt2); |
Unsafe類中的compareAndSwapInt,是一個本地方法,該方法的實現位于unsafe.cpp中
| 123456 | 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);??jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);??return(jint)(Atomic::cmpxchg(x, addr, e)) == e;UNSAFE_END |
如果是Linux的x86,Atomic::cmpxchg方法的實現如下:
| 12345678 | inline jint Atomic::cmpxchg (jint exchange_value, volatilejint* dest, jint compare_value) {??intmp = 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");??returnexchange_value;} |
看到這匯編,內心崩潰
__asm__表示匯編的開始
volatile表示禁止編譯器優化
LOCK_IF_MP是個內聯函數
| 1 | #define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: " |
Window的x86實現如下:
| 12345678910111213141516171819 | inline jint Atomic::cmpxchg (jint exchange_value, volatilejint* dest, jint compare_value) {????intmp = os::isMP(); //判斷是否是多處理器????_asm {????????mov edx, dest????????mov ecx, exchange_value????????mov eax, compare_value????????LOCK_IF_MP(mp)????????cmpxchg dword ptr [edx], ecx????}}// Adding a lock prefix to an instruction on MP machine// VC++ doesn't like the lock prefix to be on a single line// so we can't insert a label after the lock prefix.// By emitting a lock prefix, we can define a label after it.#define LOCK_IF_MP(mp) __asm cmp mp, 0?\???????????????????????__asm je L0????? \???????????????????????__asm _emit 0xF0\???????????????????????__asm L0: |
LOCK_IF_MP根據當前系統是否為多核處理器決定是否為cmpxchg指令添加lock前綴。
intel手冊對lock前綴的說明如下:
上面的第2點和第3點所具有的內存屏障效果,保證了CAS同時具有volatile讀和volatile寫的內存語義。
CAS缺點
CAS存在一個很明顯的問題,即ABA問題。
問題:如果變量V初次讀取的時候是A,并且在準備賦值的時候檢查到它仍然是A,那能說明它的值沒有被其他線程修改過了嗎?
如果在這段期間曾經被改成B,然后又改回A,那CAS操作就會誤認為它從來沒有被修改過。針對這種情況,java并發包中提供了一個帶有標記的原子引用類AtomicStampedReference,它可以通過控制變量值的版本來保證CAS的正確性。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔為你收集整理的面试必问的 CAS ,要多了解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国充电联盟:9 月公共充电桩环比增加
- 下一篇: 中国充电联盟:9月公共充电桩环比增加19