Java CAS原子操作

530 阅读2分钟

这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

i++问题

经过之前的学习,我们知道了i++这个操作分为了三个步骤:

  1. CPU从内存读取变量i的值
  2. CPU执行i+1操作
  3. CPU将第二部操作的结果赋值给变量i

上面三个步骤通过volatile关键字,可以保证1、3的原子性以及禁止使用高速缓存。但三个操作合并在一起就无法保证操作的原子性了。

CAS解决i++原子性

对于上面的i++问题,jdk提供了Atomic原子类解决i++原子操作,阅读各种博客的我也了解到这是由CAS实现的,并且达到了无锁编程,但是CAS真的是无锁吗?

假设定义以下变量,CAS的执行过程有以下几个步骤

    private volatile long val;
  1. 通过val内存地址读取val的值
  2. 比较内存地址的值与1中读取的值是否相等
  3. 相等,将val内存地址指向的变量跟新为修改值
  4. 不等返回

下面我们来思考上面操作是否存在并发安全问题,判断一套操作是否存在并发安全问题,别无他法,只能在每一个写操作的地方想象上下文切换的动作,对比上下文切换是否对最终结果有影响。按照这个方法,你会发现,如果两个线程A,B同时执行到2,然后线程A进行上下文切换,线程B继续执行3,4操作。随后喜线程A恢复执行3,4步骤从而导致线程B的写入对A不可见。

这个问题的解决办法有俩:

  • 在步骤1之前加锁,只需要把这个内存地址锁住,不让线程读取即可
  • 将2,3两个操作变为一个原子性操作

思考完后,来瞧瞧jdk如何实现的。先找到AtomicInteger#getAndIncrement()方法:

image.png

image.png

image.png

我已经下载了最新版openjdk源码,用vsocde打开,ctrl+shift+f 全局搜索compareAndSwapInt

image.png

image.png

![image.png](p9-juejin.byteimg.com/tos-cn-i-k3…? 就不一个一个列举方法了,找了一个Linux x86实现,直接到最后,最后是通过CPU提供的cmpxchgb指令实现比较赋值的操作,注意前面的lock指令,volatile通过lock禁用高速缓存,将变量刷回主内存,并且通过缓存锁定内存地址dest使得其它CPU核心此时无法读取该内存地址值以此来保证步骤2、3原子性。翻阅intel cmpxchgb指令文档,也说明了该指令不具备原子性(www.felixcloutier.com/x86/cmpxchg… 需要小飞机)。

image.png