video 的使用(video.min.js)
CAS是什麼
CAS是compare and swap的縮寫中文翻譯為比較並替換
我們都知道,在java語言之前,併發就已經廣泛存在並在伺服器領域得到了大量的應用。所以硬體廠商老早就在晶片中加入了大量直至併發操作的原語,從而在硬體層面提升效率。在intel的CPU中,使用cmpxchg指令。
在Java發展初期,java語言是不能夠利用硬體提供的這些便利來提升系統的效能的。而隨著java不斷的發展,Java本地方法(JNI)的出現,使得java程式越過JVM直接呼叫本地方法提供了一種便捷的方式,因而java在併發的手段上也多了起來。而在Doug Lea提供的cucurenct包中,CAS理論是它實現整個java包的基石。
CAS 操作包含三個運算元 —— 記憶體位置(V)、預期原值(A)和新值(B)。 如果記憶體位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新為新值 。否則,處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前返回該 位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前 值。)CAS 有效地說明了“我認為位置 V 應該包含值 A;如果包含該值,則將 B 放到這個位置;否則,不要更改該位置,只告訴我這個位置現在的值即可。”
通常將 CAS 用於同步的方式是從地址 V 讀取值 A,執行多步計算來獲得新 值 B,然後使用 CAS 將 V 的值從 A 改為 B。如果 V 處的值尚未同時更改,則 CAS 操作成功。
類似於 CAS 的指令允許演算法執行讀-修改-寫操作,而無需害怕其他執行緒同時 修改變數,因為如果其他執行緒修改變數,那麼 CAS 會檢測它(並失敗),演算法 可以對該操作重新計算。
CAS的缺點
- 迴圈時間長資源佔用問題,如果替換失敗,執行緒會自旋,直到替換為止,會佔用CPU資源
- ABA問題
- 只能保證一個共享變數的原子操作
資源佔用問題
//Unsafe.class public 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; }
例如:getAndAddInt方法內部使用CAS,通過原始碼可知如果當前執行緒沒有比較並替換成功,當前執行緒會迴圈執行下去直到替換成功,假如該執行緒一直未替換成功,那麼此執行緒會一直執行下去,會佔用CPU排程資源。
ABA問題
因為CAS需要在操作值的時候檢查下主記憶體的值是否發生變化,如果沒有發生變化就交換,但是假如一個值原先是A,變成了B,又變成了A,那麼使用CAS進行檢查的時候是未發現此值的變化的,但實際上它是變化的了。ABA問題的解決思路就是使用版本號。在變數前面追加上版本號,每次變數更新的時候把版本號加一,那麼A-B-A 就會變成1A-2B-3A。
如何避免ABA問題
從Java1.5開始JDK的atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。
public class CasDemo {
public static void main(String[] args) {
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
//執行緒t1,負責完成ABA的執行
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "當前stamp:" + atomicStampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(atomicStampedReference.getReference(),atomicStampedReference.getReference() + 1
,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
atomicStampedReference.compareAndSet(atomicStampedReference.getReference(),100
,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
},"t1").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "當前stamp:" + atomicStampedReference.getStamp());
int stamp = atomicStampedReference.getStamp();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(atomicStampedReference.getReference(),atomicStampedReference.getReference() + 1
,stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "執行結果:" + result);
},"t2").start();
while (Thread.activeCount() > 2) {
}
}
}