1.什么是CAS
CAS(Compare And Swap)比較並替換,是線程並發運行時用到的一種技術;
2.CAS作用
樂觀鎖
3.其他鎖機制缺點
在JDK 5之前Java語言是靠synchronized關鍵字保證同步的,這會導致有鎖。
鎖機制存在以下問題:
(1)在多線程競爭下,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題。
(2)一個線程持有鎖會導致其它所有需要此鎖的線程掛起。
(3)如果一個優先級高的線程等待一個優先級低的線程釋放鎖會導致優先級倒置,引起性能風險。
volatile是不錯的機制,但是volatile不能保證原子性。因此對於同步最終還是要回到鎖機制上來。
獨占鎖是一種悲觀鎖,synchronized就是一種獨占鎖,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。而另一個更加有效的鎖就是樂觀鎖。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。
4.CAS實現
CAS需要三個指令,分別是內存位置(JAVA中的內存地址,V),舊的預期值(A)和新值(B)。CAS執行時,當且僅當V符合預期A的時候,新值更新V的值,否則不執行更新,但是最終都會返回V的舊值,上述的處理過程就是一個原子操作。
(圖片來源網絡)
比如:要將上圖中的CPU1要將56更新為57,會比對CPU1拿到的舊值是不是56,如果是就更新;而CPU2拿到的舊值是55≠內存值56,所以更新失敗。
JDK1.5之后才可以使用CAS操作,例如Unsafe類中就大量使用CAS操作,還有J.U.C並發包中也有使用。
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);
這里引用一下《深入理解JVM》中的一個例子:
/** * Atomic自增測試 */ public class AtomicTest { public static AtomicInteger race = new AtomicInteger(); public static void increase() { race.incrementAndGet(); } public static final int THREAD_COUNT = 20; public static void main(String[] args) { Thread[] threads = new Thread[THREAD_COUNT]; for (int i = 0; i < THREAD_COUNT; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 10000; j++) { increase(); } } }); threads[i].start(); } while (Thread.activeCount() > 1) { Thread.yield(); } //race :200000 System.out.println("race :" + race); } }
多線程下,程序執行得到正確的結果,主要是 AtomicInteger的incrementAndGet()方法的原子性。
4."ABA"問題
CAS可以有效的提升並發的效率,但同時也會引入ABA問題。
如線程1從內存X中取出A,這時候另一個線程2也從內存X中取出A,
並且線程2進行了一些操作將內存X中的值變成了B,然后線程2又將內存X中的數據變成A,
這時候線程1進行CAS操作發現內存X中仍然是A,然后線程1操作成功。
雖然線程1的CAS操作成功,但是整個過程就是有問題的。比如鏈表的頭在變化了兩次后恢復了原值,但是不代表鏈表就沒有變化。
所以JAVA中提供了AtomicStampedReference/AtomicMarkableReference來處理會發生ABA問題的場景,主要是在對象中額外再增加一個標記(版本號)來標識對象是否有過變更。
總結:
- CAS(Compare And Swap)比較並替換,是線程並發運行時用到的一種技術
- CAS是原子操作,保證並發安全,而不能保證並發同步
- CAS是CPU的一個指令(需要JNI調用Native方法,才能調用CPU的指令)
- CAS是非阻塞的、輕量級的樂觀鎖