ABA以及原子更新引用
1 什么是ABA?
一句话:狸猫换太子。
详情:假如存在两个线程t1,t2,他们都将物理内存中的数据A复制到自己的工作内存中,t1的任务执行时间比较长,t2的执行时间短,t2将A改为B,然后又改回A,此时走人,t1回来之后发现符合CAS规则,然后将其修改为新的值==>尽管线程t1的cas操作成功,但是不代表这个过程就是没有问题的。
cas认为头和尾一致的时候,资源就没有被修改,过程是否修改是未知的,如果过程也不能被修改,就会出现问题。
解决方式:原子引用+时间戳。
2 原子引用
AtomicReferenceDemo:
package com.at.cas;
import jdk.nashorn.internal.objects.annotations.Getter;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author : code1997
* @date :2020-09-2020/9/10 18:36
*/
public static void main(String[] args) throws InterruptedException {
User user1 = new User("张三", 22);
User user2 = new User("李四", 23);
AtomicReference<User> userAtomicReference = new AtomicReference<>();
//原子变量user1
userAtomicReference.set(user1);
//true User{userName='李四', age=23}
System.out.println(userAtomicReference.compareAndSet(user1, user2)+"\t"+userAtomicReference.get());
new Thread(){
@Override
public void run() {
User user = userAtomicReference.get();
user.setAge(66);
userAtomicReference.compareAndSet(user1,user);
}
}.start();
//等待上面的线程执行结束
Thread.sleep(1000);
//false User{userName='李四', age=66}
System.out.println(userAtomicReference.compareAndSet(user1, user2)+"\t"+userAtomicReference.get());
}
class User{
String userName;
int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
3 时间戳+原子引用
之前我们了解到CAS存在ABA问题,AtomicStampedReference类。
官方描述:An {@code AtomicStampedReference} maintains an object reference along with an integer “stamp”, that can be updated atomically:维护对象引用以及可以自动更新的整数
ABADemo:
package com.at.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author : code1997
* @date :2020-09-2020/9/10 19:07
*/
public class ABADemo {
static AtomicReference<Integer> integerAtomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> integerAtomicStampedReference=new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
System.out.println("==================1.ABA产生==================");
new Thread(()->{
integerAtomicReference.compareAndSet(100,101);
integerAtomicReference.compareAndSet(101,100);
},"t1").start();
new Thread(()->{
try {
//暂停1s,等待t1完成ABA的操作。
TimeUnit.SECONDS.sleep(1);
//true 108 我们发现可以更新成功
System.out.println(integerAtomicReference.compareAndSet(100, 108)+
"\t"+integerAtomicReference.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2").start();
try {
//保证上面任务执行结束
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=================2.ABA的解决=================");
new Thread(()->{
int stamp = integerAtomicStampedReference.getStamp();
//t3第1次版本号为:1
System.out.println(Thread.currentThread().getName()+"第1次版本号为:"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//进行ABA操作
stamp=integerAtomicStampedReference.getStamp();
//t3 true 2
System.out.println(Thread.currentThread().getName()+"\t"+integerAtomicStampedReference.compareAndSet(100, 101,stamp, stamp + 1) +"\t"+integerAtomicStampedReference.getStamp());
stamp=integerAtomicStampedReference.getStamp();
//t3 true 3
System.out.println(Thread.currentThread().getName()+"\t"+integerAtomicStampedReference.compareAndSet(101, 100,stamp, stamp + 1) +"\t"+integerAtomicStampedReference.getStamp());
},"t3").start();
new Thread(()->{
int stamp = integerAtomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"第1次版本号为:"+stamp);
try {
//等待t3完成一次ABA操作
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//t4 false 3
System.out.println(Thread.currentThread().getName()+"\t"+integerAtomicStampedReference.compareAndSet(100, 2020,stamp, stamp + 1) +"\t"+integerAtomicStampedReference.getStamp());
//t4 当前实际的值:100
System.out.println(Thread.currentThread().getName()+"\t当前实际的值:"+integerAtomicStampedReference.getReference());
},"t4").start();
}
}