CAS 即 compare and swap,比较并交换。java
CAS是一种原子操做,同时 CAS 使用乐观锁机制。安全
J.U.C中的不少功能都是创建在 CAS 之上,各类原子类,其底层都用 CAS来实现原子操做。用来解决并发时的安全问题。并发
i++
public class AddTest { public volatile int i; public void add() { i++; } }
经过javap -c AddTest
能够看到add 方法的字节码指令:性能
public void add(); Code: 0: aload_0 1: dup 2: getfield #2 // Field i:I 5: iconst_1 6: iadd 7: putfield #2 // Field i:I 10: return
i++
被拆分红了多个指令:this
getfield
拿到原始内存值;iadd
进行加 1 操做;putfield
写把累加后的值写回内存。假设一种状况:atom
线程 1
执行到iadd
时,因为尚未执行putfield
,这时候并不会刷新主内存区中的值。线程 2
进入开始运行,刚刚将主内存区的值拷贝到私有内存区。线程 1
正好执行putfield
,更新主内存区的值,那么此时线程 2
的副本就是旧的了。错误就出现了。最简单的,在 add 方法加上 synchronized 。线程
public class AddTest { public volatile int i; public synchronized void add() { i++; } }
虽然简单,而且解决了问题,可是性能表现并很差。code
最优的解法应该是使用JDK自带的CAS方案,如上例子,使用AtomicInteger
类对象
public class AddIntTest { public AtomicInteger i; public void add() { i.getAndIncrement(); } }
CAS 的原理并不复杂:内存
拿 AtomicInteger
类分析,先来看看源码:
我这里的环境是Java11,若是是Java8这里一些内部的一些命名有些许不一样。
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; /* * This class intended to be implemented using VarHandles, but there * are unresolved cyclic startup dependencies. */ private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe(); private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value"); private volatile int value; //... }
Unsafe
类,该类对通常开发而言,少有用到。
Unsafe
类底层是用 C/C++ 实现的,因此它的方式都是被 native 关键字修饰过的。
它能够提供硬件级别的原子操做,如获取某个属性在内存中的位置、修改对象的字段值。
关键点:
AtomicInteger
类存储的值在 value
字段中,而value
字段被volatile
在静态代码块中,而且获取了 Unsafe
实例,获取了 value
字段在内存中的偏移量 VALUE
接下回到刚刚的例子:
如上,getAndIncrement()
方法底层利用 CAS 技术保证了并发安全。
public final int getAndIncrement() { return U.getAndAddInt(this, VALUE, 1); }
getAndAddInt()
方法:
public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!weakCompareAndSetInt(o, offset, v, v + delta)); return v; }
v
经过 getIntVolatile(o, offset)
方法获取,其目的是获取 o
在 offset
偏移量的值,其中 o
就是 AtomicInteger
类存储的值,即value
, offset
内存偏移量的值,即 VALUE
。
重点,weakCompareAndSetInt
就是实现 CAS 的核心方法
o
和 v
相等,就证实没有其余线程改变过这个变量,那么就把 v
值更新为 v + delta
,其中 delta
是更新的增量值。分析:
AtomicInteger
的原始值为 A,线程 1
和线程 2
各自持有一份副本,值都是 A。线程 1
经过getIntVolatile(o, offset)
拿到 value 值 A,这时线程 1
被挂起。线程 2
也经过getIntVolatile(o, offset)
方法获取到 value 值 A,并执行weakCompareAndSetInt
方法比较内存值也为 A,成功修改内存值为 B。线程 1
恢复执行weakCompareAndSetInt
方法比较,发现本身手里的值 A 和内存的值 B 不一致,说明该值已经被其它线程提早修改过了。线程 1
从新执行getIntVolatile(o, offset)
再次获取 value 值,由于变量 value 被 volatile 修饰,具备可见性,线程A继续执行weakCompareAndSetInt
进行比较替换,直到成功CAS是由CPU支持的原子操做,其原子性是在硬件层面进行保证的,在Java中普通用户没法直接使用,只能借助atomic
包下的原子类使用,灵活性受限。
可是CAS只能保证单个变量操做的原子性,当涉及到多个变量时,CAS无能为力。
原子性也不必定能保证线程安全,如在Java中须要与volatile
配合来保证线程安全。
CAS 有一个问题,举例子以下:
线程 1
从内存位置 V 取出 A线程 2
也从内存位置 V 取出 A线程 1
处于挂起状态,线程 2
将位置 V 的值改为 B,最后再改为 A线程 1
再执行,发现位置 V 的值没有变化,符合指望继续执行。此时虽然线程 1
仍是成功了,可是这并不符合咱们真实的指望,等于线程 2
狸猫换太子把线程 1
耍了。
这就是所谓的ABA问题
引入原子引用,带版本号的原子操做。
把咱们的每一次操做都带上一个版本号,这样就能够避免ABA问题的发生。既乐观锁的思想。
内存中的值每发生一次变化,版本号都更新。
在进行CAS操做时,比较内存中的值的同时,也会比较版本号,只有当两者都没有变化时,才能执行成功。
Java中的AtomicStampedReference
类即是使用版本号来解决ABA问题的。
在并发冲突几率大的高竞争环境下,若是CAS一直失败,会一直重试,CPU开销较大。
针对这个问题的一个思路是引入退出机制,如重试次数超过必定阈值后失败退出。
更重要的是避免在高竞争环境下使用乐观锁。