我们都知道CAS操作是一个原子操作,但是再多线程环境下就会产生ABA问题。所以Java提供了这个类来解决ABA问题
import java.util.concurrent.atomic.AtomicReference;
/**
* MyTest.
*
* @author 2021/7/21 9:04 下午
*/
class User{
private String name;
private Integer score;//积分
public User(String name, Integer score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
public class MyTest {
/**
* 场景描述:假设再电子商务系统中,用户购买物品后,需要该用户的积分增加。此时是一个并发的增加积分系统
* @param args
*/
public static void main(String[] args) {
/**使用原子引用。根据Atomic***可以实现CAS操作,并且避免ABA问题。他提供了AtomicInteger, AtomicBoolean, AtomicLong....类
* 但是我们使用的操作User类。并不适合,所以可以使用泛型的类AtomicReference<V>
*/
AtomicReference<User> reference = new AtomicReference<>();//设置User为原子引用
User zhangsan = new User("zhangsan", 1000);//假设这条记录来自数据库
User zhangsansan = new User("zhangsan",1500);//这条数据来自系统,购买物品后的新的积分
reference.set(zhangsan);//设置主物理内存是张三
//此时多线程在这里进入
boolean b = reference.compareAndSet(zhangsan, zhangsansan);//如果线程私有内存和主内存相同就更新
boolean b1 = reference.compareAndSet(zhangsan, zhangsansan);//假设第二条线程也来修改
if (b){
System.out.println("线程一更新成功");
System.out.println(reference.get().toString());
}else {
System.out.println("线程一更新失败");
System.out.println(reference.get().toString());
}
if (b1){
System.out.println("线程二更新成功");
System.out.println(reference.get().toString());
}else {
System.out.println("线程二更新失败");
System.out.println(reference.get().toString());
}
}
}
线程一更新成功
User{name='zhangsan', score=1500}
线程二更新失败
User{name='zhangsan', score=1500}
上面是基于基本的原子应用,但是我们并不知道修改了多少次,如果说线程一修改完成后,接着有就该回去,线程二发现没变,就对他修改了,这就是ABA问题,
接下来我们看看看基于时间戳的原子引用
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* MyTest2.
*
* @author 2021/7/21 9:44 下午
*/
class User2{
private String name;
private Integer score;//积分
public User2(String name, Integer score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
public class MyTest2 {
private static volatile int stamp=1;
public static void main(String[] args) {
User2 zhangsan = new User2("zhangsan", 1000);//假设这条记录来自数据库
User2 zhangsansan = new User2("zhangsan",1500);//这条数据来自系统,购买物品后的新的积分
//设置User对象为原子引用,且版本号为1
AtomicStampedReference<User2> reference = new AtomicStampedReference<>(zhangsan, stamp);
//线程一
int stamp = reference.getStamp();//拿到版本号
boolean b = reference.compareAndSet(zhangsan, zhangsansan, reference.getStamp(), reference.getStamp() + 1);
int stamp1 = reference.getStamp();
boolean b1 = reference.compareAndSet(zhangsansan, zhangsan, reference.getStamp(), reference.getStamp() + 1);//线程一讲结果修改回去
int stamp2 = reference.getStamp();
//线程二
boolean b2 = reference.compareAndSet(zhangsan, zhangsansan, stamp, stamp + 1);//线程二很乐观,认为版本号还是1.其实已经是3了
int stamp3 = reference.getStamp();
if (b){
System.out.println("线程一修改成功 当前版本号是"+stamp1);
}
if (b1){
System.out.println("线程一rollback成功 当前版本号是"+stamp2);
}
if (b2){
System.out.println("线程二修改成功 当前版本号是"+stamp3);
}else {
System.out.println("线程二修改失败 当前版本号是"+stamp3);
}
}
}
线程一修改成功 当前版本号是2
线程一rollback成功 当前版本号是3
线程二修改失败 当前版本号是3
以上操作不是很理想,但是大致思路就是这样的。有不对之处欢迎大家改正