CAS全称 Compare And Swap(比较与交换),在不使用锁的情况下实现多线程之间的变量同步。属于硬件同步原语,处理器提供了基本内存操作的原子性保证。juc包中的原子类就是通过CAS来实现了乐观锁。
CAS算法涉及到三个操作数:
需要读写的内存值 V。
进行比较的旧值A (期望操作前的值)
要写入的新值 B。
当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。
一般情况下,“更新”是一个不断重试的过程。
Java中的sun.misc.Unsafe类,提供了
compareAndSwapInt
compareAndSwapLong
等方法实现CAS。
示例
J.U.C包内的原子操作封装类看一下AtomicInteger的源码定义: public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value;
各属性的作用:
unsafe: 获取并操作内存的数据
valueOffset: 存储value在AtomicInteger中的偏移
value: 存储AtomicInteger的int值,该属性需要借助volatile关键字保证其在线程间的可见性
接着查看自增方法incrementAndGet的源码时,发现自增函数底层调用的是unsafe.getAndAddInt。
但是由于JDK本身只有Unsafe.class,只通过class文件中的参数名,并不能很好的了解方法的作用,所以我们通过OpenJDK 8 来查看Unsafe的源码:
由源码可看出,getAndAddInt()循环获取给定对象o中的偏移量处的值v,然后判断内存值是否等于v。
如果相等则将内存值设置为 v + delta
否则返回false,继续循环进行重试,直到设置成功才能退出循环,并且将旧值返回
整个“比较+更新”操作封装在compareAndSwapInt()中,通过JNI使用CPU指令完成的,属于原子操作,可以保证多个线程都能够看到同一个变量的修改值。
JDK通过CPU的cmpxchg指令,去比较寄存器中的 A 和 内存中的值 V。如果相等,就把要写入的新值 B 存入内存中。如果不相等,就将内存值 V 赋值给寄存器中的值 A。然后通过Java代码中的while循环再次调用cmpxchg指令进行重试,直到设置成功为止。
CAS的问题 循环+CAS自旋的实现让所有线程都处于高频运行,争抢CPU执行时间的状态。CAS操作如果长时间不成功,会导致其一直自旋,如果操作长时间不成功,会带来很大的CPU资源消耗。
只能保证一个共享变量的原子操作