ABA問題的產生
CAS會導致“ABA問題”。
CAS算法實現一個重要前提需要取出內存中某時刻的數據並在當下時刻比較並替換,那么在這個時間差類會導致數據的變化。
比如說一個線程1從內存位置V中取出A,這時候另一個線程2也從內存中取出A,並且線程2進行了一些操作將值變成了B,然后線程2又將V位置的數據 變成了A,這時候線程1進行CAS操作發現內存中仍然是A,然后線程1操作成功。只關注開始和結尾,不關心中間過程。
盡管線程1的CAS操作成功,但是不代表這個過程就是沒有問題的。
演示ABA問題的產生
/**
* 演示ABA問題產生
*/
public class ABADemo1 {
public static AtomicReference<Integer> atomicReference=new AtomicReference<>(100);
public static void main(String[] args) {
System.out.println("------------ABA問題的產生----------------");
new Thread(()->{
//100改101再改回100
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").start();
new Thread(()->{
//暫停1s,保證上面的t1完成一次ABA操作
try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) { e.printStackTrace();}
//只看結果值,不看過程
System.out.println(atomicReference.compareAndSet(100,2019)+"---"+atomicReference.get());;
},"t2").start();
}
}
ABA問題解決
理解原子引用+新增一種機制,那就是修改版本號(類似時間戳)
先了大概解下原子引用
java.util.concurrent.atomic的atomic包里AtomicReference。
AtomicReference是作用是對”對象”進行原子操作。提供了一種讀和寫都是原子性的對象引用變量。原子意味着多個線程試圖改變同一個AtomicReference(例如比較和交換操作)將不會使得AtomicReference處於不一致的狀態。
/**
* AtomicReference原子引用
*/
public class AtomicReferenceDemo {
public static void main(String[] args) {
User user1= new User("張三",22);
User user2= new User("李四",23);
AtomicReference<User> atomicReference=new AtomicReference<>();
//設置主物理內存值為user1
atomicReference.set(user1);
//現在比較並交換
System.out.println(atomicReference.compareAndSet(user1,user2)+"-----"+atomicReference.get());
System.out.println(atomicReference.compareAndSet(user1,user2)+"-----"+atomicReference.get());
}
}
@Data
@AllArgsConstructor
class User{
private String name;
private int age;
}
然后再用JDK的atomic包里提供了一個類AtomicStampedReference來解決ABA問題。
簡單說就是加個類似時間戳的標志,也就是說每一次修改只需要設置不同的版本好即可。如果當前引用 == 預期引用,並且當前標志等於預期標志,則以原子方式將該引用和該標志的值設置為給定的更新值。
演示ABA問題解決
/**
* ABA問題解決AtomicStampedReference
*/
public class ABADemo2 {
public static AtomicStampedReference<Integer> atomicStampedReference=new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
System.out.println("------------ABA問題的解決----------------");
new Thread(()->{
//獲取版本號
int stamp=atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+" 第1次版本號:"+stamp+
"\t 當前值:"+atomicStampedReference.getReference());
//暫停1s為了讓t4獲取到同一版本號
try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) { e.printStackTrace();}
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+" 第2次版本號:"+atomicStampedReference.getStamp()+
"\t當前值:"+atomicStampedReference.getReference());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+" 第3次版本號:"+atomicStampedReference.getStamp()+
"\t當前值:"+atomicStampedReference.getReference());
},"t3").start();
new Thread(()->{
//獲取版本號
int stamp=atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+" 第1次版本號:"+stamp+"\t當前值:"+atomicStampedReference.getReference());
//暫停3s,保證上面的t3完成一次ABA操作
try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) { e.printStackTrace();}
//t4還是用傻傻地用上面獲取的版本號
boolean result=atomicStampedReference.compareAndSet(100,2019,stamp,stamp+1);
System.out.println(Thread.currentThread().getName()+" 修改結果:"+result);
System.out.println(Thread.currentThread().getName()+"當前版本號:"+atomicStampedReference.getStamp()+
"\t當前最新值"+atomicStampedReference.getReference());
},"t4").start();
}
}
