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();
}
}