CAS算法實作一個重要前提需要取出記憶體中某時刻的資料,而在下時刻比較并替換,那麼在這個時間差類會導緻資料的變化。
上篇文章講到CAS會出現一個ABA問題。那什麼是ABA問題呢?
官方一點的解釋就是:當有多個線程對一個原子類進行操作的時候,某個線程在短時間内将原子類的值A修改為B,又馬上将其修改為A,此時其他線程不感覺,還是會修改成功。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAjM2EzLcd3LcJzLcJzdllmVldWYtl2PnVGcq5iZjJzYphmN58WNvwVO2QTN3QjNtUGall3LcVmdhNXLwRHdo9CXt92YucWbpRWdvx2Yx5yazF2Lc9CX6MHc0RHaiojIsJye.jpeg)
代碼案例:
//線程操作資源,原子類ai的初始值為4
static AtomicInteger ai = new AtomicInteger(4);
public static void main(String[] args) {
new Thread(() -> {
//利用CAS将ai的值改成5
boolean b = ai.compareAndSet(4, 5);
System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改為5:"+b);
//休眠一秒
try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
//利用CAS将ai的值改回4
b = ai.compareAndSet(5,4);
System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改為4:"+b);
},"A").start();
new Thread(() -> {
//模拟此線程執行較慢的情況
try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
//利用CAS将ai的值從4改為10
boolean b = ai.compareAndSet(4, 10);
System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改為10:"+b);
},"B").start();
//等待其他線程完成,為什麼是2,因為一個是main線程,一個是背景的GC線程
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("ai最終的值為:"+ai.get());
}
複制
執行結果:
可以看到,線程B最終是将ai的值修改成功了。
上面例子模拟的是A、B兩個線程操作一個資源ai,A的執行速度比B的快,在B執行前,A就已經将ai的值改為5之後馬上又把ai的值改回為4,但是B不感覺,是以最後B就修改成功了。
比如有兩個單身狗A、B,A在某個時間段内找到女朋友但是又分開了,但是沒告訴B,此時B還是會在A是單身狗的情況下帶A去打遊戲。
ABA問題的解決方案?
資料庫有個鎖稱為樂觀鎖,是一種基于資料版本實作資料同步的機制,每次修改一次資料,版本就會進行累加。
同樣,Java也提供了相應的原子引用類AtomicStampedReference<V>
上圖中的初始郵票就是版本号。
根據之前的代碼改動的例子:
static AtomicStampedReference<Integer> ai = new AtomicStampedReference<>(4,0);
public static void main(String[] args) {
new Thread(() -> {
//四個參數分别是預估記憶體值,更新值,預估版本号,初始版本号
//隻有當預估記憶體值==實際記憶體值相等并且預估版本号==實際版本号,才會進行修改
boolean b = ai.compareAndSet(4, 5,0,1);
System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改為5:"+b);
try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
b = ai.compareAndSet(5,4,1,2);
System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改為4:"+b);
},"A").start();
new Thread(() -> {
try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
boolean b = ai.compareAndSet(4, 10,0,1);
System.out.println(Thread.currentThread().getName()+"是否成功将ai的值修改為10:"+b);
},"B").start();
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("ai最終的值為:"+asri.getReference());
}
複制
運作結果:
可以看到,最終B并沒有成功修改ai的值