java 线程aba,JAVA中CAS-ABA的问题解决方案AtomicStampedReference
了解CAS(Compare-And-Swap)
CAS即對比交換,它在保證數據原子性的前提下盡可能的減少了鎖的使用,很多編程語言或者系統實現上都大量的使用了CAS。
JAVA中CAS的實現
JAVA中的cas主要使用的是Unsafe方法,Unsafe的CAS操作主要是基于硬件平臺的匯編指令,目前的處理器基本都支持CAS,只不過不同的廠家的實現不一樣罷了。
Unsafe提供了三個方法用于CAS操作,分別是
public final native boolean compareAndSwapObject(Object value, long valueOffset, Object expect, Object update);
public final native boolean compareAndSwapInt(Object value, long valueOffset, int expect, int update);
public final native boolean compareAndSwapLong(Object value, long valueOffset, long expect, long update);
復制代碼
value 表示 需要操作的對象
valueOffset 表示 對象(value)的地址的偏移量(通過Unsafe.objectFieldOffset(Field valueField)獲取)
expect 表示更新時value的期待值
update 表示將要更新的值
具體過程為每次在執(zhí)行CAS操作時,線程會根據valueOffset去內存中獲取當前值去跟expect的值做對比如果一致則修改并返回true,如果不一致說明有別的線程也在修改此對象的值,則返回false
Unsafe類中compareAndSwapInt的具體實現:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
復制代碼
ABA問題
線程1準備用CAS修改變量值A,在此之前,其它線程將變量的值由A替換為B,又由B替換為A,然后線程1執(zhí)行CAS時發(fā)現變量的值仍然為A,所以CAS成功。但實際上這時的現場已經和最初不同了。
aba.png
例子
public static AtomicInteger a = new AtomicInteger(1);
public static void main(String[] args){
Thread main = new Thread(() -> {
System.out.println("操作線程" + Thread.currentThread() +",初始值 = " + a); //定義變量 a = 1
try {
Thread.sleep(1000); //等待1秒 ,以便讓干擾線程執(zhí)行
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean isCASSuccess = a.compareAndSet(1,2); // CAS操作
System.out.println("操作線程" + Thread.currentThread() +",CAS操作結果: " + isCASSuccess);
},"主操作線程");
Thread other = new Thread(() -> {
a.incrementAndGet(); // a 加 1, a + 1 = 1 + 1 = 2
System.out.println("操作線程" + Thread.currentThread() +",【increment】 ,值 = "+ a);
a.decrementAndGet(); // a 減 1, a - 1 = 2 - 1 = 1
System.out.println("操作線程" + Thread.currentThread() +",【decrement】 ,值 = "+ a);
},"干擾線程");
main.start();
other.start();
}
復制代碼
// 輸出
> 操作線程Thread[主操作線程,5,main],初始值 = 1
> 操作線程Thread[干擾線程,5,main],【increment】 ,值 = 2
> 操作線程Thread[干擾線程,5,main],【decrement】 ,值 = 1
> 操作線程Thread[主操作線程,5,main],CAS操作結果: true
復制代碼
解決ABA方案
思路
解決ABA最簡單的方案就是給值加一個修改版本號,每次值變化,都會修改它版本號,CAS操作時都對比此版本號。
aba_2.png
JAVA中ABA中解決方案(AtomicStampedReference)
AtomicStampedReference主要維護包含一個對象引用以及一個可以自動更新的整數"stamp"的pair對象來解決ABA問題。
//關鍵代碼
public class AtomicStampedReference {
private static class Pair {
final T reference; //維護對象引用
final int stamp; //用于標志版本
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static Pair of(T reference, int stamp) {
return new Pair(reference, stamp);
}
}
private volatile Pair pair;
....
/**
* expectedReference :更新之前的原始值
* newReference : 將要更新的新值
* expectedStamp : 期待更新的標志版本
* newStamp : 將要更新的標志版本
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair current = pair; //獲取當前pair
return
expectedReference == current.reference && //原始值等于當前pair的值引用,說明值未變化
expectedStamp == current.stamp && // 原始標記版本等于當前pair的標記版本,說明標記未變化
((newReference == current.reference &&
newStamp == current.stamp) || // 將要更新的值和標記都沒有變化
casPair(current, Pair.of(newReference, newStamp))); // cas 更新pair
}
}
復制代碼
例子
private static AtomicStampedReference atomicStampedRef =
new AtomicStampedReference<>(1, 0);
public static void main(String[] args){
Thread main = new Thread(() -> {
System.out.println("操作線程" + Thread.currentThread() +",初始值 a = " + atomicStampedRef.getReference());
int stamp = atomicStampedRef.getStamp(); //獲取當前標識別
try {
Thread.sleep(1000); //等待1秒 ,以便讓干擾線程執(zhí)行
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1); //此時expectedReference未發(fā)生改變,但是stamp已經被修改了,所以CAS失敗
System.out.println("操作線程" + Thread.currentThread() +",CAS操作結果: " + isCASSuccess);
},"主操作線程");
Thread other = new Thread(() -> {
atomicStampedRef.compareAndSet(1,2,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
System.out.println("操作線程" + Thread.currentThread() +",【increment】 ,值 = "+ atomicStampedRef.getReference());
atomicStampedRef.compareAndSet(2,1,atomicStampedRef.getStamp(),atomicStampedRef.getStamp() +1);
System.out.println("操作線程" + Thread.currentThread() +",【decrement】 ,值 = "+ atomicStampedRef.getReference());
},"干擾線程");
main.start();
other.start();
}
復制代碼
// 輸出
> 操作線程Thread[干擾線程,5,main],【increment】 ,值 = 2
> 操作線程Thread[干擾線程,5,main],【decrement】 ,值 = 1
> 操作線程Thread[主操作線程,5,main],初始值 a = 1
> 操作線程Thread[主操作線程,5,main],CAS操作結果: true
復制代碼
總結
以上是生活随笔為你收集整理的java 线程aba,JAVA中CAS-ABA的问题解决方案AtomicStampedReference的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: lnmp里面php测试脚本,军哥LNMP
- 下一篇: matlab gui自定义图片做背景,m