1.CAS導致ABA問題:
CAS算法實現一個重要前提需要取出內存中某時刻的數據並在當下時刻比較並交換,那么在這個時間差中會導致數據的變化。
比如:線程1從內存位置V中取出A,這時線程2也從V中取出A,線程2進行了一些操作將值改成了B,然后線程2又將V的數據改回A;此時線程1進行CAS操作發現內存中仍然是A,然后線程1操作成功。
盡管線程1的CAS操作成功,但是不代表這個過程就是沒有問題的。
解決ABA問題:利用原子引用+修改版本號(類似時間戳),每次需要獲取到版本最新的值進行處理。
2.原子引用AtomicReference
ABA問題出現示例:
package com.mort.test; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class TestAtomicReference { static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100); public static void main(String[] args) { new Thread(() -> { // 執行ABA操作 atomicReference.compareAndSet(100, 101); atomicReference.compareAndSet(101, 100); }, "t1").start(); new Thread(()->{ // 暫停1秒鍾t2線程,保證t1完成了一次ABA操作 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicReference.compareAndSet(100, 2019)+"\t" + atomicReference.get()); }, "t2").start(); } }
3.解決ABA問題:時間戳原子引用AtomicStampedReference
代碼示例:
package com.mort.test; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; public class TestABASolve { //static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100); static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1); public static void main(String[] args) { new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t第1次版本號:"+stamp); // 暫停1秒鍾t1線程,保證t2拿到版本號與t1相同 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 執行ABA操作 atomicStampedReference.compareAndSet(100, 101,stamp,stamp+1); stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t第2次版本號:"+stamp); atomicStampedReference.compareAndSet(101, 100, stamp,stamp+1); // System.out.println(Thread.currentThread().getName()+"\t第3次版本號:"+atomicStampedReference.getStamp()); }, "t1").start(); new Thread(()->{ int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t第1次版本號:"+stamp); // 暫停3秒鍾t2線程,保證t1完成了一次ABA操作 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp+1); System.out.println(Thread.currentThread().getName()+"\t執行結果:"+result+"\t最新版本號:"+atomicStampedReference.getStamp()); System.out.println(Thread.currentThread().getName()+"\t當前實際最新值:"+atomicStampedReference.getReference()); }, "t2").start(); } }
輸出結果:
t1 第1次版本號:1 t2 第1次版本號:1 t1 第2次版本號:2 t2 執行結果:false 最新版本號:3 t2 當前實際最新值:100