ABA问题分析
产生原因
CAS会导致ABA问题
CAS算法实现一个重要的前提是需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化.
比如说一个线程one从内存位置V中取出A,线程Two也执行,将A–>B–>A,这时one进行CAS操作发现内存仍是A,然后one操作成功.
one操作成功,但是在这个过程中线程two可能操作了其它数据,产生问题.
package com.ygy.juc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* 产生ABA问题
*/
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(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100,2019)+"\t"+atomicReference.get());
}).start();
}
}
解决办法
如果线程one操作不被中断,那么问题就解决了
- 将类变成原子类
- 操作过程添加版本号
原子引用
在juc包中有默认的原子类型原子操作类,如果不满足需求,可以自己创造原子类型,用原子引用,AtomicReference,可以包装类,达到操作不被中断的目的
package com.ygy.juc;
import java.util.concurrent.atomic.AtomicReference;
/**
* @Description: TODO(原子引用)
* @author yangguangyuan
* @date 2019年6月14日
*
*/
public class AtomicReferenceDemo {
public static void main(String[] args) {
User z3 = new User("zhangsan", 23);
User li4 = new User("lisi", 23);
AtomicReference<Object> atomicReference = new AtomicReference<>();
atomicReference.set(z3);
System.out.println(atomicReference.compareAndSet(z3, li4) + "\t"
+ atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(z3, li4) + "\t"
+ atomicReference.get().toString());
}
}
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 + "]";
}
}
问题
虽然已经变成原子类,但是还是没有解决ABA问题
时间戳原子引用
原子引用+新增机制(修改版本号,类似时间戳)
package com.ygy.juc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 解决ABA问题
*/
public class ABADemo2 {
static AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
new Thread(()->{
int stamp = reference.getStamp();
System.out.println("第1次版本号:"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
reference.compareAndSet(100,101,reference.getStamp(),reference.getStamp()+1);
System.out.println("第2次版本号:"+reference.getStamp());
reference.compareAndSet(101,100,reference.getStamp(),reference.getStamp()+1);
System.out.println("第3次版本号:"+reference.getStamp());
},"t3").start();
new Thread(()->{
int stamp = reference.getStamp();
System.out.println("第1次版本号:"+stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = reference.compareAndSet(100,2019,stamp,stamp+1);
System.out.println(Thread.currentThread().getName()+"修改是否成功:"+result+"\t当前最新实际版本号:"+reference.getStamp());
System.out.println(Thread.currentThread().getName()+"\t当前实际最新值:"+reference.getReference());
},"t4").start();
}
}