说说无锁CAS与Unsafe类

156 阅读3分钟

分析&回答

无锁CAS

CAS的全称是Compare And Swap 即比较交换,其算法核心思想执行函数:CAS(V,E,N) 其包含3个参数:V表示要更新的变量、E表示预期值、N表示新值。如果V值等于E值,则将V的值设为N。若V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。

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;
}

CAS与Java内存模型(JMM)结合理解
CAS无锁机制 三个参数:
V :需要更新的变量   **主内存中的**
E :预期值         **本地内存的**
N :新值                  
如果:
    V=E  说明没有被修改过   将V的值设为N
    V!=E 说明被修改过      主内存中的值刷新到本地内存,然后进行循环比较,一致了就改为N

CPU指令对CAS的支持

CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

Unsafe类

Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存。
Java中CAS操作的执行依赖于Unsafe类的方法,Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。 上面介绍 CAS 的代码就是 Unsafe 中的源码。

反思&扩展

CAS的ABA问题及其解决方案

CAS是当且仅当旧的预期值E和内存值V相同时,才将内存值V修改为U,也就是如果内存值V没有发生变化则更新,但是有可能发生内存值原来是A,中间被改成B,后来又被改成A,此时再使用CAS进行检查时发现没有变化,但是实际上发生了变化,这就是ABA问题。如下图

image.png

ABA问题的解决方案则是使用时间戳原子引用,在原子引用上新增版本号(类似时间戳)。
Java并发包下的AtomicStampedReference可以解决ABA问题,内部实现上添加了一个类似于版本号作用的stamp属性,它是被自动更新的。实现上首先检查当前引用是否等于预期引用、当前stamp是否等于预期stamp,如果全部相等,则以原子方式将该引用和该stamp的值设置为给定的更新值。

自旋锁的问题

自旋锁是一种假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起,这种方式确实也是可以提升效率的。但问题是当线程越来越多竞争很激烈时,占用CPU的时间变长会导致性能急剧下降,因此Java虚拟机内部一般对于自旋锁有一定的次数限制,可能是50或者100次循环后就放弃,直接挂起线程,让出CPU资源。

CAS操作也是通过不断的自循环(while循环)实现,不过这种循环的结束条件是线程成功更新对于的值,但也是自旋锁的一种。


喵呜面试助手: 一站式解决面试问题,你可以搜索微信小程序 [喵呜面试助手] 或关注 [喵呜刷题] -> 面试助手 免费刷题。如有好的面试知识或技巧期待您的共享!