前言:只知道CAS是比较修改貌似在面试中还是不够,面试还需要知道CAS的缺陷,CAS的底层实现,这里以AtomicInteger为例进行分析,CAS的是干什么的这种不再赘述。
参考:
https://www.cnblogs.com/renqiqiang/articles/10129354.html
目录
第一章 AtomicInteger中涉及到CAS的函数
经过对AtomicInteger源代码的查看,发现用到CAS主要是下面的函数,观察下面的函数,发现主要是用到了这个几个unsafe的函数,unsafe是封装了原子操作的JNI,使用unsafe能把操作原子化,原子操作是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换。
unsafe.getAndSetInt
unsafe.compareAndSwapInt
unsafe.getAndAddInt
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
第二章 分析unsafe的几个方法
观察getAndSetInt、getAndAddInt的源码,发现它们都调用了compareAndSwapInt函数,那么可以看到传入了四个参数,
第一个参数代表要修改字段所在的对象,如我们其实传入了this,this也就代表AtomicInteger。
第二个参数代表valueOffset代表要修改字段相对于我们的对象在内存中的偏移量,如我们要修改AtomicInteger中的value,传入的就是value的偏移量,字段的偏移量在AtomicInteger的静态代码块(如下)中计算出(感觉这也间接说明静态字段在内存中的位置不会变?)
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
第三个参数代表修改前的值,即我们期望的修改前的值。
第四个参数代表修改后的值,即我们期望的修改后的值。
//AtomicInteger.class
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
//Unsafe.class
public final int getAndSetInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var4));
return var5;
}
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;
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
第三章 CAS的缺陷
参考:https://www.cnblogs.com/renqiqiang/articles/10129354.html
3.1 ABA问题
问题描述:线程t1将它的值从A变为B,再从B变为A。同时有线程t2要将值从A变为C。但CAS检查的时候会发现没有改变,但是实质上它已经发生了改变 。可能会造成数据的缺失。
解决方法:CAS还是类似于乐观锁,同数据乐观锁的方式给它加一个版本号或者时间戳,如AtomicStampedReference
3.2 多次自旋导致效率下降
问题描述:多个线程争夺同一个资源时,如果自旋一直不成功,将会一直占用CPU。
解决方法:破坏掉for死循环,当超过一定时间或者一定次数时,return退出。JDK8新增的LongAddr,和ConcurrentHashMap类似的方法。当多个线程竞争时,将粒度变小,将一个变量拆分为多个变量,达到多个线程访问多个资源的效果,最后再调用sum把它合起来。
虽然base和cells都是volatile修饰的,但感觉这个sum操作没有加锁,可能sum的结果不是那么精确。
3.3 多变量共享一致性问题
我们之前都是对一个变量使用循环CAS,如果对多个变量操作,1. 可以加锁来解决。2 .封装成对象类解决。