CAS缺点,以及ABA问题和解决办法
java中多线程之CAS(compareAndSet),Unsafe类大白话详解.
CAS缺点
- 循环时间长,开销大
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
如果this.compareAndSwapInt(var1, var2, var5, var5 + var4)期望值判断一直为False(当前线程比较倒霉,总有其他线程在我前面进行过操作),就会一直循环,给CPU带来很大的开销。
- 只能保证一个共享变量的原子操作
- ABA问题
什么是ABA问题。
看一个流程案例:- 现在有1、2两个线程进来操作同一个变量A
- 1线程跑得快,把A改成了B
- 1线程又把B改回了A
- 2线程来通过CAS想把A改成B,发现内存中的值是A,符合自己的期望值,以为中途没有线程对它进行更改,就把值改为了B
整个过程虽然操作成功,但不代表过程没有问题!
如何解决ABA问题呢?
新建一个User类
class User{
String name;
public User(String name) {
this.name = name;
}
}
测试:通过AtomicReference创建我们自定义的User类的原子引用。
public static void main(String[] args) {
User user1 = new User("张三");
User user2 = new User("李四");
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(user1);
System.out.println(atomicReference.compareAndSet(user1, user2));
System.out.println(atomicReference.get().name);
}
上面的例子很简单哈,通过cas将原子类的引用从张三改为李四。
先不管上面的代码,现在我们提出一个办法来解决ABA问题:修改版本号(时间戳),类似乐观锁
重写我们的测试代码:用上AtomicStampedReference。使用AtomicStampedReference中的compareAndSet(var1,var2,var3,var4)方法
var1:期望值
var2:修改值
var3:期望版本号
var4:修改版本号
public class CasDemo {
static User user1 = new User("张三");
static AtomicReference<User> atomicReference = new AtomicReference<>(user1);
//初始化版本号为1,变量为张三
static AtomicStampedReference<User> atomicStampedReference = new AtomicStampedReference<>(user1,1);
public static void main(String[] args) {
new Thread(()->{
User user2 = new User("李四");
//获取版本号
int num = atomicStampedReference.getStamp();
System.out.println("线程1第一次获取到版本号"+num);
//ABA操作
atomicStampedReference.compareAndSet(user1,user2,1,2);
atomicStampedReference.compareAndSet(user2,user1,2,3);
},"线程1").start();
new Thread(()->{
//获取版本号
int num = atomicStampedReference.getStamp();
System.out.println("线程2第一次获取到版本号"+num);
//暂停一秒,保证线程1完成了一次ABA操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
User user2 = new User("李四");
atomicStampedReference.compareAndSet(user1,user2,1,2);
System.out.println(atomicReference.get().name);
},"线程2").start();
}
}
1. 线程1,开始ABA操作,版本化递增到了3。
2. 线程2,开始ABA操作,由于版本化期望值对比已经改变,修改无效,解决了ABA问题