CAS机制
我们知道volatile修饰的变量具有可见性和有序性,但是没有原子性.
比如a++的过程分为3步
- 从内存中读取a
- 对a进行加1
- 将a的值重新写入到内存.
在多线程中,可能有一个线程读完后,还没有写入内存就被其他线程读走了.
解决这个问题可以用AtomicInteger,代码修改了volatile的例子
public class Test {
private static AtomicInteger a=new AtomicInteger();
public static void main(String[] args) {
Thread[] threads=new Thread[5];
for(int i=0;i<5;i++){
threads[i]=new Thread(()->{
for(int j=0;j<10;j++){
try {
System.out.println(a.incrementAndGet());
Thread.sleep(1000);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
threads[i].start();
}
}
}
查看一下incrementAndGet()的源码
public final int incrementAndGet(){
return unsafe.getAndAddInt(this,valueOffset,1)+1;
}
unsafe调用了getAndAddInt方法
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;
}
底层调用的compareAndSwapInt方法,即cas机制.
分析CAS
过程:包含3个参数,CAS(V,E,N),V表示要更新变量的值,E表示预期,N表示新值.当V等于E,才会将V的值设为N,如果V值和E值不相同,这说明已经有其他线程做了更新,当前线程则什么都不做.
底层原理
public final class Unsafe(){
public final native boolean compareAndSwapInt(
Object o,
long offset,
int expect,
int x);
//还有很多方法
}
用的是C语言实现
解决ABA问题
AtomicStampedReference是一个带有时间戳的对象引用,能够很好的解决ABA问题.
public class AtomicTest {
private static AtomicInteger index=new AtomicInteger(10);
static AtomicStampedReference<Integer> stampRef=new AtomicStampedReference<Integer>(10, 1);
public static void main(String[] args) {
new Thread(()->{
int stamp=stampRef.getStamp();
System.out.println(Thread.currentThread().getName()+"第1次版本号"+stamp);
stampRef.compareAndSet(10, 11, stampRef.getStamp(), stampRef.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"第2次版本号"+stampRef.getStamp());
stampRef.compareAndSet(11, 10,stampRef.getStamp(), stampRef.getStamp()+1 );
System.out.println(Thread.currentThread().getName()+"第3次版本号"+stampRef.getStamp());
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
int stamp=stampRef.getStamp();
System.out.println(Thread.currentThread().getName()+"第1次版本号"+stamp);
boolean isSuccess=stampRef.compareAndSet(10, 12, stampRef.getStamp(), stampRef.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"修改是否成功"+isSuccess+"当前版本"+stampRef.getStamp());
System.out.println(Thread.currentThread().getName()+"当前实际值"+stampRef.getReference());
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
使用的AtomicStampedReference的compareAndSet函数,里面有4个参数
- 预期值
- 要更新的值
- 预期的时间戳
- 更新的时间戳