吃透CAS
作者:互联网
什么是CAS
CAS是compare-And-Swap的缩写,意思是比较并交换。它是一条cpu并发原语。它的功能是判断内存某个位置的值是否为预期值。如果是则更改为新的值。这个过程是原子的。因为原语的执行必须是连续的,在执行过程中不允许被中断。也就是说CAS是一条cpu的原子指令。不会造成数据不一致问题。
java中的魔法类sun.misc.Unsafe
Unsafe,先混个脸熟。Unsafe的底层实现是cas的核心类,Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。想要吃透JUC下的AtomicXXX,Lock。Unsafe是绕不开的类。不求深入到底层的C/C++源码,但求能了解它的基本功能。查看Unsafe的源码我们会发现。我们直接调用会直接报错,因为Unsafe仅供java内部类使用。
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
不过我们可以想办法通过反射拿到Unsafe类的实例
@Test
public void test() throws NoSuchFieldException, IllegalAccessException {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
}
AtomicInteger示例
看看AtomicInteger在没有锁的情况下是如何做到数据正确性的。
static {
try {
//这里拿到了变量value的内存地址并赋值给valueOffset
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//源码中,这是定义的变量值,用volatile原语,保证线程间的数据是可见的。这样才获取变量的值的时候才能直接读取
private volatile int value;
//不通线程可直接读取,因为value是volatile修饰
public final int get() {
return value;
}
然后来看看自增是怎么做到的并发安全的
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;
}
//unsafe的本地方法,也是cas的核心
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
Object var1:代表当前对象
long var2:代表内存偏移量,相当于对象值的引用地址
int var4:代表期望值,使用期望值和当前对象中的值进行比较
int var5:代表要交换的值特别注意:compareAndSwapInt的实现原理是根据var1和var2算出内存中的真实值,和var4比较,为true则更新为var5。
因为特别重要,是最核心的东西举例再解释一次
假设A线程和B线程同时执行getAndAddInt操作:
1.AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据jmm模型,A线程和B线程各自持有一个值为3的value的副本到各自的工作内存。
2.线程A通过getIntVolatile(var1,var2)拿到var5的值3,这是A线程被挂起。
3.线程B也通过getIntVolatile(var1,var2)拿到var5的值3,此时刚好线程B没有被挂起并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为4,线程B完成执行。
4.这时线程A恢复,执行compareAndSwapInt方法,该方法通过前两个参数var1,var2发现自己的值3(var4)和主内存数字4(var4)不一致,返回false,那A线程本次修改失败,只能重读重取重新来一遍。
5.线程再循环一次getIntVolatile(var1,var2)拿到var5的值4,因为变量value被volatile修饰。所以其他线程对它的修改,线程A能够看到,线程A继续执行compareAndSwapInt方法比较,直到成功。
CAS的优点
1.避免加互斥锁
2.避免线程的切换提高程序运行效率
CAS的缺点
1.循环时间长开销大:因为有do while循环。如果cas失败,会一直尝试。高并发的场景,就有可能导致 CAS 一直都操作不成功,这样的话,循环时间就会越来越长。CPU 资源也是一直在被消耗的,这会对性能产生很大的影响。所以根据实际情况来选择是否使用 CAS,在高并发的场景下,通常 CAS 的效率是不高的
解决方式:并发不算太高的时候选择CAS
2.只能保证一个共享变量的原子操作,不能同时保证多个对象的原子性
解决方案:利用一个新的类,来整合刚才这一组共享变量,这个新的类中的多个成员变量就是刚才的那多个共享变量,然后再利用 atomic 包中的 AtomicReference 来把这个新对象整体进行 CAS 操作,这样就可以保证线程安全。
3.重头戏:ABA问题 详情点击
标签:var5,吃透,CAS,Unsafe,int,线程,var1 来源: https://www.cnblogs.com/gaoweiBlog/p/14837983.html