久久综合九色综合97婷婷-美女视频黄频a免费-精品日本一区二区三区在线观看-日韩中文无码有码免费视频-亚洲中文字幕无码专区-扒开双腿疯狂进出爽爽爽动态照片-国产乱理伦片在线观看夜-高清极品美女毛茸茸-欧美寡妇性猛交XXX-国产亚洲精品99在线播放-日韩美女毛片又爽又大毛片,99久久久无码国产精品9,国产成a人片在线观看视频下载,欧美疯狂xxxx吞精视频

有趣生活

當前位置:首頁>職場>cas什么崗位(面試必問的CAS要多了解)

cas什么崗位(面試必問的CAS要多了解)

發布時間:2024-01-24閱讀(10)

導讀前言CAS(CompareandSwap),即比較并替換,實現并發算法時常用到的一種技術,Douglea大神在java同步器中大量使用了CAS技術,鬼斧神工....

cas什么崗位(面試必問的CAS要多了解)(1)

前言

CAS(Compare and Swap),即比較并替換,實現并發算法時常用到的一種技術,Doug lea大神在java同步器中大量使用了CAS技術,鬼斧神工的實現了多線程執行的安全性。

CAS的思想很簡單:三個參數,一個當前內存值V、舊的預期值A、即將更新的值B,當且僅當預期值A和內存值V相同時,將內存值修改為B并返回true,否則什么都不做,并返回false。

cas什么崗位(面試必問的CAS要多了解)(2)

問題

一個n 的問題。

public class Case { public volatile int n; public void add() { n ; }}

通過javap -verbose Case看看add方法的字節碼指令

public void 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 被拆分成了幾個指令:

  1. 執行getfield拿到原始n;
  2. 執行iadd進行加1操作;
  3. 執行putfield寫把累加后的值寫回n;

通過volatile修飾的變量可以保證線程之間的可見性,但并不能保證這3個指令的原子執行,在多線程并發執行下,無法做到線程安全,得到正確的結果,那么應該如何解決呢?

如何解決

在add方法加上synchronized修飾解決。

public class Case { public volatile int n; public synchronized void add() { n ; }}

這個方案當然可行,但是性能上差了點,還有其它方案么?

再來看一段代碼

public int a = 1;public boolean compareAndSwapInt(int b) { if (a == 1) { a = b; return true; } return false;}

如果這段代碼在并發下執行,會發生什么?

假設線程1和線程2都過了a==1的檢測,都準備執行對a進行賦值,結果就是兩個線程同時修改了變量a,顯然這種結果是無法符合預期的,無法確定a的最終值。

解決方法也同樣暴力,在compareAndSwapInt方法加鎖同步,變成一個原子操作,同一時刻只有一個線程才能修改變量a。

除了低性能的加鎖方案,我們還可以使用JDK自帶的CAS方案,在CAS中,比較和替換是一組原子操作,不會被外部打斷,且在性能上更占有優勢。

下面以AtomicInteger的實現為例,分析一下CAS是如何實現的。

public class AtomicInteger extends Number implements java.io.Serializable { // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; public final int get() {return value;}}

  1. Unsafe,是CAS的核心類,由于Java方法無法直接訪問底層系統,需要通過本地(native)方法來訪問,Unsafe相當于一個后門,基于該類可以直接操作特定內存的數據。
  2. 變量valueOffset,表示該變量值在內存中的偏移地址,因為Unsafe就是根據內存偏移地址獲取數據的。
  3. 變量value用volatile修飾,保證了多線程之間的內存可見性。

看看AtomicInteger如何實現并發下的累加操作:

public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta);}//unsafe.getAndAddIntpublic final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 var4)); return var5;}

假設線程A和線程B同時執行getAndAdd操作(分別跑在不同CPU上):

  1. AtomicInteger里面的value原始值為3,即主內存中AtomicInteger的value為3,根據Java內存模型,線程A和線程B各自持有一份value的副本,值為3。
  2. 線程A通過getIntVolatile(var1, var2)拿到value值3,這時線程A被掛起。
  3. 線程B也通過getIntVolatile(var1, var2)方法獲取到value值3,運氣好,線程B沒有被掛起,并執行compareAndSwapInt方法比較內存值也為3,成功修改內存值為2。
  4. 這時線程A恢復,執行compareAndSwapInt方法比較,發現自己手里的值(3)和內存的值(2)不一致,說明該值已經被其它線程提前修改過了,那只能重新來一遍了。
  5. 重新獲取value值,因為變量value被volatile修飾,所以其它線程對它的修改,線程A總是能夠看到,線程A繼續執行compareAndSwapInt進行比較替換,直到成功。

整個過程中,利用CAS保證了對于value的修改的并發安全,繼續深入看看Unsafe類中的compareAndSwapInt方法實現。

public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);

Unsafe類中的compareAndSwapInt,是一個本地方法,該方法的實現位于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); jint* addr = (jint *) index_oop_from_field_offset_long(p, offset); return (jint)(Atomic::cmpxchg(x, addr, e)) == e;UNSAFE_END

  1. 先想辦法拿到變量value在內存中的地址。
  2. 通過Atomic::cmpxchg實現比較替換,其中參數x是即將更新的值,參數e是原內存的值。

如果是Linux的x86,Atomic::cmpxchg方法的實現如下:

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;}

看到這匯編,內心崩潰

__asm__表示匯編的開始

volatile表示禁止編譯器優化

LOCK_IF_MP是個內聯函數

#define LOCK_IF_MP(mp) "cmp $0, " #mp "; je 1f; lock; 1: "

Window的x86實現如下:

inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) { int mp = 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 doesnt like the lock prefix to be on a single line// so we cant 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前綴。

  1. 如果是多處理器,為cmpxchg指令添加lock前綴。
  2. 反之,就省略lock前綴。(單處理器會不需要lock前綴提供的內存屏障效果)

Intel手冊對lock前綴的說明如下:

  1. 確保后續指令執行的原子性。
  2. 在Pentium及之前的處理器中,帶有lock前綴的指令在執行期間會鎖住總線,使得其它處理器暫時無法通過總線訪問內存,很顯然,這個開銷很大。在新的處理器中,Intel使用緩存鎖定來保證指令執行的原子性,緩存鎖定將大大降低lock前綴指令的執行開銷。
  3. 禁止該指令與前面和后面的讀寫指令重排序。
  4. 把寫緩沖區的所有數據刷新到內存中。

上面的第2點和第3點所具有的內存屏障效果,保證了CAS同時具有volatile讀和volatile寫的內存語義。

CAS缺點

CAS存在一個很明顯的問題,即ABA問題。

問題:如果變量V初次讀取的時候是A,并且在準備賦值的時候檢查到它仍然是A,那能說明它的值沒有被其他線程修改過了嗎?

如果在這段期間曾經被改成B,然后又改回A,那CAS操作就會誤認為它從來沒有被修改過。針對這種情況,java并發包中提供了一個帶有標記的原子引用類AtomicStampedReference,它可以通過控制變量值的版本來保證CAS的正確性。

cas什么崗位(面試必問的CAS要多了解)(3)

TAGS標簽:  什么  崗位  面試  必問  要多  cas什么崗位(面試

歡迎分享轉載→http://www.avcorse.com/read-213071.html

Copyright ? 2024 有趣生活 All Rights Reserve吉ICP備19000289號-5 TXT地圖HTML地圖XML地圖