无锁非阻塞同步
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对象都是直接获取的
1 | private static final Unsafe unsafe = Unsafe.getUnsafe(); |
然而自己的代码照着写会报错SecurityException,原因是Java限制了只能在类库中调用,有个比较取巧的方法是用反射去获取名为theUnsafe
的内部实例
1 | private static final Unsafe UNSAFE; |
使用时还需要调整IDE的配置,否则会被处理成Error阻止编译
Preferences-Java-Complicer-Errors/Warnings里面的Deprecated and restricted API中的Forbidden references(access rules)选为Warning就可以编译通过
虽然可以用手段获取Unsafe实例,但是内部API随时变更,不建议在开发时使用
版权声明
This site by Linest is licensed under a Creative Commons BY-NC-ND 4.0 International License.
由Linest创作并维护的博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证。
本文永久链接:http://linest.github.io/2017/09/06/java-multithread-cas/