Java CAS

无锁非阻塞同步

CAS 原理


CAS(Compare and Swap)需要提供目标地址,之前旧值和要更改的新值。在写入的时候先比较目标地址上的最新值和旧值,如果相等说明没被别人改过,此时写入新值,否则放弃写入
这个比较—交换看上去是两段过程,但是由native方法操作底层操作系统,对应单条CPU指令cmpxchg,保证过程原子不被打断
不管是否写入,都返回开始这个操作时地址上的值,即如果替换,返回原值,如果没替换,返回就是现在的最新值

CAS 优点


加锁可以保证线程安全,但是阻塞并发性比较差,采用无锁lock-free的CAS实现可以显著提升性能并且回避锁的一系列弊端

  • 提高性能,线程不需要阻塞唤醒过程,避免了上下文切换和调度延迟
  • 避免死锁、饥饿等生存性问,单个线程出问题不会波及其他线程,不会出现持锁线程不放,其他等待无法运行的情况

加锁是悲观机制,假定存在同时修改,严格排他保护,CAS是乐观机制,进行尝试操作,假定没有同时修改,CAS加锁失败后外围逻辑一般会发起重新尝试
竞争激烈时,CAS一致自旋尝试,会浪费CPU

JDK应用


CAS是原子类及其他并发类实现的基石,广泛应用于进行线程调度,垃圾收集,锁实现等。在Java中,sun.misc.Unsafe类提供了相关功能
比如java.util.concurrent.atomic.AtomicInteger是典型实现

Unsafe


Java类库中使用的unsafe对象都是直接获取的

Sample
1
private static final Unsafe unsafe = Unsafe.getUnsafe();

然而自己的代码照着写会报错SecurityException,原因是Java限制了只能在类库中调用,有个比较取巧的方法是用反射去获取名为theUnsafe的内部实例

Sample
1
2
3
4
5
6
7
8
9
10
private static final Unsafe UNSAFE;
static {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
UNSAFE = (Unsafe) theUnsafe.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
}

使用时还需要调整IDE的配置,否则会被处理成Error阻止编译
Preferences-Java-Complicer-Errors/Warnings里面的Deprecated and restricted API中的Forbidden references(access rules)选为Warning就可以编译通过
虽然可以用手段获取Unsafe实例,但是内部API随时变更,不建议在开发时使用