以下内容基于Java1.8编写
首先需要明白一点,CAS的全称是 __compare and switch__,也就是比较与交换,目的在于对 set 做非阻塞原子操作,常用于多线程环境。
atomic包下的CAS
atomic
包下的类主要是为了对单一数据进行原子操作,比如AtomicInteger
、AtomicReference
、AtomicStampedReference
等。
这些类中的get
和set
方法几乎都是简单的取值和赋值,并未做线程安全优化,只有像是compareAndSet
这类的方法才是原子操作。
1 | public class AtomicInteger extends Number implements java.io.Serializable { |
从源码中可以看出,这些方法主要还是调用的unsafe
类的方法。在不知道unsafe是什么的情况下,只需要知道,atomic
调用的这些方法都是原子操作就行了。
Unsafe中的CAS
首先从名字上看就可以看出这个类是不安全的,主要原因在于它提供了很多内存有关的方法,这导致了JVM管理的内存机制可能会失效,并且会出现类似于 C 中的指针问题。就像是反射一样,错误使用可能会导致很多不可预料的问题。
Unsafe类的获取
Unsafe
是一个单例模型,并不能直接new
出来,而是通过Unsafe.getUnsafe()
的方式获取。但如果你真的这样写了,你会发现程序运行到这里时会抛出异常:java.lang.SecurityException: Unsafe
。
1 | public final class Unsafe { |
从源码中可以看出,在调用getUnsafe
方法时,会检测当前的调用类是否会通过isSystemDomainLoader
方法校验。
也就是说,只有被引导类加载器加载的类才允许调用Unsafe
方法。所以如果我们需要调用Unsafe
类的话,一般用的都是反射方式:
1 | public class UnsafeTime { |
Unsafe的CAS调用
Unsafe
类不止 CAS 方法,但篇幅有限,这里只介绍 CAS 方法。
1 | public final class Unsafe { |
这里用int方法举例,参数列表为:
var1
- 需要修改的值的对象var2
- 修改的目的值在内存中偏移量,一般可以通过unsafe.objectFieldOffset
来获取,其参数是Field
,也就是从类的存储结构中自动定位目的值偏移量。var4
- 期望值,也就是为了避免在调用时有其他线程同步修改,这里需要判断期望值是否与目的值相同。如果不同则表示此时数据已被修改,返回false
。var5
- 目标值,也就是set
过去的值。只有当此方法返回true
时才会set
成功。
举例:
1 | public class Test { |
控制台打印结果如下:
1 | 小明 |
ABA问题
ABA问题的出现主要是compare
的时候,可能其他线程同步修改了数据后,数据和期望值相同,但此时的逻辑流程已经不是compare
时的流程了。
CAS 为了解决这类问题,提供了类似版本号的一个数值,在compare
期望值时,会同时校验版本号是否相同。
其他
ConcurrentHashMap
中的putVal
方法其实就是用的 CAS 和synchronized
关键字实现的。ReentrantLock
的NonfairSync
也是使用了 CAS 的方式进行状态设定。