CAS算法概述
CAS是英文單詞CompareAndSwap的縮寫,中文意思是:比較並替換。CAS需要有3個操作數:內存地址V,舊的預期值A,即將要更新的目標值B。
CAS指令執行時,當且僅當內存地址V的值與預期值A相等時,將內存地址V的值修改為B,否則就什么都不做。整個比較並替換的操作是一個原子操作。
注:t1,t2線程是同時更新同一變量56的值
因為t1和t2線程都同時去訪問同一變量56,所以他們會把住內存的值完全拷貝一份到自己的工作內存空間,所以t1和t2線程值都為56
假設t1和t2在線程競爭中線程t1能去更新變量值改為57,而其他線程都失敗。(失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次發起嘗試)。T1線程去更新變量值改為57,然后寫到內存中。此時對於t2來說,內存值改為57,與預期值56不一致,就操作失敗了(想改的值不再是原來的值)。
CAS算法的開銷主要來自Cache Miss,一旦導致CAS一直更新失敗的話,它的性能是有可能壞於加鎖的方式的。
ABA問題概述
上面我我們說了CAS算法,CAS實現的過程是先取出內存中某時刻的數據,在下一時刻比較並替換,那么在這個時間差會導致數據的變化,此時就會導致出現“ABA”問題。關於“ABA”問題,我們假設如下事件序列:
線程 1 從內存位置V中取出A。
線程 2 從位置V中取出A。
線程 2 進行了一些操作,將B寫入位置V。
線程 2 將A再次寫入位置V。
線程 1 進行CAS操作,發現位置V中仍然是A,操作成功。
盡管線程 1 的CAS操作成功,但不代表這個過程沒有問題——對於線程 1 ,線程 2 的修改已經丟失。
我們形象地畫一個圖來打個比方:
解決方法
我們在AtomicReference的使用中就遇到了這樣的ABA問題,name怎么解決的呢?我們使用AtomicStampedReference就能很好的解決這個問題了,首先,我們先看一下這一段代碼(實現自動充值,當少於20元時,充值20元,再進行消費,每次消費10元):
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicReferenceDemo { static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 1); static Object obj = new Object(); public static void main(String[] args) { Add add = new Add(); Thread thread1 = new Thread(add); thread1.start(); Reduce reduce = new Reduce(); Thread thread2 = new Thread(reduce); thread2.start(); } static class Add implements Runnable { @Override public void run() {while (true) { Integer m = money.getReference(); synchronized (obj) { // 1處 if (m < 20) { if (money.compareAndSet(m, m + 20, money.getStamp(), money.getStamp() + 1)) { //2處 System.out.println("充值成功,余額:" + money.getReference() + "元"); break; } } else { break; } } } } } static class Reduce implements Runnable { @Override public void run() { while (true) { while (true) { Integer m = money.getReference(); synchronized (obj) { //3處 if (m > 10) { if (money.compareAndSet(m, m - 10, money.getStamp(), money.getStamp() + 1)) { //4處 System.out.println("成功消費10元,余額:" + money.getReference() + "元"); break; } } else { break; } } } try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
我們在1處2處加上鎖之后就OK了!這是結果:
這樣問題就很好的解決了,不過特別注意的是在1處和3處需要加鎖,因為2處和4處的if條件是一個原子操作,大家都知道,java是搶占式的,線程可能在這個原子操作執行結束后被另一個線程所搶占,這樣就是導致打印的時候的值不准確。