ABA問題產生及解決方案


1、基本的ABA問題

在CAS算法中,需要取出內存中某時刻的數據(由用戶完成),在下一時刻比較並交換(CPU保證原子操作),這個時間差會導致數據的變化。 假設有以下順序事件: > 1、線程1從內存位置V中取出A > 2、線程2從內存位置V中取出A > 3、線程2進行了寫操作,將B寫入內存位置V > 4、線程2將A再次寫入內存位置V > 5、線程1進行CAS操作,發現V中仍然是A,交換成功

盡管線程1的CAS操作成功,但線程1並不知道內存位置V的數據發生過改變

2、ABA問題示例

``` public class ABADemo {
private static AtomicReference<Integer> atomicReference = new AtomicReference<Integer>(100);

public static void main(String[] args) {
	new Thread(() -> {
		atomicReference.compareAndSet(100, 101);
		atomicReference.compareAndSet(101, 100);
	},"t1").start();
	
	new Thread(() -> {
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(atomicReference.compareAndSet(100, 2019) + "\t修改后的值:" + atomicReference.get());
	},"t2").start();
}

}

> * 初始值為100,線程t1將100改成101,然后又將101改回100
> * 線程t2先睡眠1秒,等待t1操作完成,然后t2線程將值改成2019
> * 可以看到,線程2修改成功

輸出結果:

true 修改后的值:2019


<h1>3、ABA問題解決</h1>
要解決ABA問題,可以增加一個版本號,當內存位置V的值每次被修改后,版本號都加1

### AtomicStampedReference
AtomicStampedReference內部維護了對象值和版本號,在創建AtomicStampedReference對象時,需要傳入初始值和初始版本號,
當AtomicStampedReference設置對象值時,對象值以及狀態戳都必須滿足期望值,寫入才會成功

#### 示例

public class ABADemo {

private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100,1);

public static void main(String[] args) {
	new Thread(() -> {
		System.out.println("t1拿到的初始版本號:" + atomicStampedReference.getStamp());
		
		//睡眠1秒,是為了讓t2線程也拿到同樣的初始版本號
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		atomicStampedReference.compareAndSet(100, 101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
		atomicStampedReference.compareAndSet(101, 100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
	},"t1").start();
	
	new Thread(() -> {
		int stamp = atomicStampedReference.getStamp();
		System.out.println("t2拿到的初始版本號:" + stamp);
		
		//睡眠3秒,是為了讓t1線程完成ABA操作
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("最新版本號:" + atomicStampedReference.getStamp());
		System.out.println(atomicStampedReference.compareAndSet(100, 2019,stamp,atomicStampedReference.getStamp() + 1) + "\t當前 值:" + atomicStampedReference.getReference());
	},"t2").start();
}

}

> 1、初始值100,初始版本號1
> 2、線程t1和t2拿到一樣的初始版本號
> 3、線程t1完成ABA操作,版本號遞增到3
> 4、線程t2完成CAS操作,最新版本號已經變成3,跟線程t2之前拿到的版本號1不相等,操作失敗

輸出結果:

t1拿到的初始版本號:1
t2拿到的初始版本號:1
最新版本號:3
false 當前 值:100


### AtomicMarkableReference
AtomicStampedReference可以給引用加上版本號,追蹤引用的整個變化過程,如:A -> B -> C -> D - > A,通過AtomicStampedReference,我們可以知道,引用變量中途被更改了3次
但是,有時候,我們並不關心引用變量更改了幾次,只是單純的關心是否更改過,所以就有了AtomicMarkableReference
AtomicMarkableReference的唯一區別就是不再用int標識引用,而是使用boolean變量——表示引用變量是否被更改過

#### 示例

public class ABADemo {

private static AtomicMarkableReference<Integer> atomicMarkableReference = new AtomicMarkableReference<Integer>(100,false);

public static void main(String[] args) {
	new Thread(() -> {
		System.out.println("t1版本號是否被更改:" + atomicMarkableReference.isMarked());
		
		//睡眠1秒,是為了讓t2線程也拿到同樣的初始版本號
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		atomicMarkableReference.compareAndSet(100, 101,atomicMarkableReference.isMarked(),true);
		atomicMarkableReference.compareAndSet(101, 100,atomicMarkableReference.isMarked(),true);
	},"t1").start();
	
	new Thread(() -> {
		boolean isMarked = atomicMarkableReference.isMarked();
		System.out.println("t2版本號是否被更改:" + isMarked);
		
		//睡眠3秒,是為了讓t1線程完成ABA操作
		try {
			TimeUnit.SECONDS.sleep(3);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("是否更改過:" + atomicMarkableReference.isMarked());
		System.out.println(atomicMarkableReference.compareAndSet(100, 2019,isMarked,true) + "\t當前 值:" + atomicMarkableReference.getReference());
	},"t2").start();
}

}

> 1、初始值100,初始版本號未被修改 false
> 2、線程t1和t2拿到一樣的初始版本號都未被修改 false
> 3、線程t1完成ABA操作,版本號被修改 true
> 4、線程t2完成CAS操作,版本號已經變成true,跟線程t2之前拿到的版本號false不相等,操作失敗

輸出結果:

t1版本號是否被更改:false
t2版本號是否被更改:false
是否更改過:true
false 當前 值:100


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM