CAS
什么是CAS
比较并交换
线程从主物理内存中拿到原始的值,经过一系列操作在准备将修改后的数据重新写回到主物理内存之前,先拿到主物理内存的真实值和期望值进行比较,如果数据没有被其他的线程修改,就可以修改数据并写回到主物理内存中,否则放弃当前的写操作,重新从主物理内存中拿到新的值重新进行比较交换操作
//如果期望值和初始值相等则修改成功,如果不相等则修改失败
//初始值是2
AtomicInteger atomicInteger = new AtomicInteger(2);
//期望值是2,修改值为3
boolean b = atomicInteger.compareAndSet(2, 3);
// true
System.out.println(b);
// 3
System.out.println(atomicInteger.get());
为什么不用synchronized用CAS
- 自旋锁 2:Unsafe 类
为什么AtomicInteger能解决线程安全问题
static {
try {
//value属性对应内存偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//volatile修饰保证可见性
private volatile int value;
//Unsafe类获取当前对象的内存偏移量的地址
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//然后进行加1的操作
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//将主内存中的值读取到自己的工作内存中(读取当前对象var1的内存地址偏移量为var2的值)
var5 = this.getIntVolatile(var1, var2);
//核心
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
//native方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
this: 当前对象
valueOffset: 内存偏移量(内存地址)
CAS compare and swap 是一条CPU并发原语,原语的执行必须是连续的,在执行过程中不允许被中断,也就是CAS是一条CPU原子指令,不会造成所谓的数据不一致的问题
CAS的缺点
- 需要多次比较,循环时间长,可能会给CPU带来很大的开销
- 只能对一个共享变量,保证原子操作,对于多个共享变量操作时,CAS无法保证操作的原子性
- 存在ABA的问题
ABA问题
数据中间有被修改,只管结果不管过程
static AtomicReference<Integer> atomicReference = new AtomicReference<>(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();
}
//修改成功,但是中间有数据变动
atomicReference.compareAndSet(100, 2019);
System.out.println(atomicReference.get());
},"t2").start();
}
原子引用
//原子引用类
AtomicReference<User> atomicReference = new AtomicReference<User>();
User user1 = new User("zhangsan",10);
User user2 = new User("lisi",10);
atomicReference.set(user1);
boolean b = atomicReference.compareAndSet(user1, user2);
System.out.println(atomicReference.get().toString());
如何解决ABA问题
加版本号
//带版本号的原子引用类,初始版本号为1
static AtomicStampedReference stampedReference = new AtomicStampedReference(100, 1);
public static void main(String[] args) {
new Thread(()->{
//保证t2拿到第一个版本号
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//将数据从100修改到101,版本号加1
stampedReference.compareAndSet(100, 101, stampedReference.getStamp(), stampedReference.getStamp()+1);
System.out.println("版本号为"+stampedReference.getStamp());
//将数据从101修改到100,版本号继续加1
stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp()+1);
System.out.println("版本号为"+stampedReference.getStamp());
},"t1").start();
new Thread(()->{
int stamp = stampedReference.getStamp();
//保证t1完成一次ABA操作
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改失败,版本号不一致
stampedReference.compareAndSet(100, 2019, stamp, stampedReference.getStamp()+1);
System.out.println("版本号为"+stampedReference.getStamp());
//输出的值还是100
System.out.println(stampedReference.getReference());
},"t2").start();
}