ABA问题
ABA问题
- AtomicInteger
CAS --> UnSafe --> CAS底层思想 --> ABA -->原子引用更新 -->如何规避ABA问题
1.介绍
1.1图解
- 所以,当A线程工作完成进行CAS时,预期值为A,真实值也为A,线程A会认为主物理内存中的值没有线程更改过,直接将其值写回主物理内存。
- 实际上只是首尾相同,中间可能有很多次变更
1.2演示
import java.util.concurrent.atomic.AtomicReference;
public class ABADemo {
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(() ->{
//暂停1s,保证t1完成了一次ABA操作
try{
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println( atomicReference.compareAndSet(100, 2019)+"\t"+atomicReference.get());
},"t2").start();
}
}
2. 如何解决ABA问题
2.1 原子引用
import java.util.concurrent.atomic.AtomicReference;
class User{
String userName;
int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
public class AtomicReferenceDemo {
public static void main(String[] args){
AtomicReference<User> atomicReference = new AtomicReference<>();
User z3 = new User("z3",22);
User l4 = new User("l4",25);
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3, l4)+"\t"+atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(z3, l4)+"\t"+atomicReference.get().toString());
}
}
2.2时间戳原子引用
//AtomicStampeReference
public class ABADemo {
static AtomicReference<Integer> atomicReference= new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100, 1);
public static void main(String[] args){
new Thread(() ->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t第一次版本号"+stamp);
try{
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();}
atomicStampedReference.compareAndSet(100,12,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t第二次版本号"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t当前值"+atomicStampedReference.getReference());
System.out.println(atomicStampedReference.compareAndSet(12,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1));
System.out.println(Thread.currentThread().getName()+"\t第三次版本号"+atomicStampedReference.getStamp());
},"t3").start();
new Thread(() ->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t第一次版本号"+stamp);
try{
TimeUnit.SECONDS.sleep(5);
}catch (InterruptedException e){
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
System.out.println(Thread.currentThread().getName()+"\t修改"+result+"\t当前版本号"+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t当前值"+atomicStampedReference.getReference());
},"t4").start();
}
}
3.问题
-
当CAS传入的新值大于等于128的时候一直修改失败
原因:Integer对-128~127间的数字有缓存,所以在此区间的current==expectedReference为true,当current>127,每次装箱将返回新的Integer对象,此时current!=expectedReference导致compareAndSet一直返回false。