CAS的三個缺點
第一個缺點:while循環會造成一直匹配不到值的循環操作,這也是CAS的一個缺點
第二個缺點:只能對一個對象進行原子操作,並不能對一塊代碼進行原子操作
第三個缺點就是接下來要講的ABA問題
ABA問題的產生:
ABA問題就是加入主存中的值爲A
(1)兩個線程都會去主存裏拿到一份值A
(2)t1線程由於執行時間短,兩秒,所以執行了又A->B->A的操作
(3)由於JMM原理,主存中也是同樣執行了這樣的操作
(4)線程t2在執行完之後看到主存中的值還是A就進行交換,但其實已經被修改過了,又改回來了
解決:
加上一個時間戳,每改變一次,自增一次,相當於版本號
使用原子引用類AtomicStampedReference
public AtomicStampedReference(V initialRef, int initialStamp) {
pair = Pair.of(initialRef, initialStamp);
}
參數一還是對象,參數二則是一個初始版本號
然後每次改變主存中的值就會改變版本號,這樣就有效 避免了ABA問題的發生
解決ABA問題demo如下
package com.wsx.aba;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
public class SloveAba {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
System.out.println("ABA問題的產生");
new Thread(()->{
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
System.out.println(Thread.currentThread().getName()+"\t"+atomicReference.get());
},"t1").start();
new Thread(()->{
try {
//爲了讓t1完成一次ABA操作
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicReference.compareAndSet(100,2019);
System.out.println(Thread.currentThread().getName()+"\t"+atomicReference.get());
},"t2").start();
//主線程睡眠等待上限操作完成
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ABA問題的解決");
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
//這裏睡一秒是爲了t3線程和t4線程的起點一致,版本號都爲1
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"\t最新版本號"+atomicStampedReference.getStamp());
//ABA帶版本號操作
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t最新版本號"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t最新版本號"+atomicStampedReference.getStamp());
},"t3").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
//這裏睡兩秒是爲了讓t3線程完成ABA操作
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//這裏的stamp是默認沒被修改的版本號,如果被其他線程修改過那麼這裏版本號就開始落後,然後修改失敗
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"\t最新版本號"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t是否改動成功"+result);
System.out.println(Thread.currentThread().getName()+"\t最新值"+atomicStampedReference.getReference());
},"t4").start();
}
}