java并发系列—CAS机制概括

CAS(无锁)

java.util.concurrent 包完全建立在 CAS 之上的

CAS 是项乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

CAS 有 3 个操作数,内存值 V,旧的预期值 A,要修改的新值 B。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不做。是一条 CPU 的原子指令,其实现方式是基于硬件平台的汇编指令,就是说 CAS 是靠硬件实现的

在没有锁的机制下可能需要借助 volatile 原语,保证线程间的数据是可见的(共享的)。这样才获取变量的值的时候才能直接读取

compareAndSet 利用 JNI (本地方法)来完成 CPU 指令的操作。

利用 CPU 的 CAS 指令,同时借助 JNI 来完成 Java 的非阻塞算法。其它原子操作都是利用类似的特性完成的。

CAS优点:

  • CAS 体现的是无锁并发、无阻塞并发,线程不会陷入阻塞,线程不需要频繁切换状态(上下文切换,系统调用)
  • CAS 是基于乐观锁的思想

CAS缺点

  1. ABA问题:当进行获取主内存值时,该内存值在写入主内存时已经被修改
    了 N 次,但是最终又改成原来的值其他线程先把 A 改成 B 又改回 A,主线程仅能判断出共享变量的值与最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,这时 CAS虽然成功,但是过程存在问题
    • 解决:引入版本号,使用AtomicStampedReference。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么 A-B-A 就会变成1A-2B-3A。
  2. 循环时间长开销大。自旋 CAS 如果长时间不成功,会给 CPU 带来非常大的执行开销,如果比较不成功一直在循环,最差的情况某个线程一直取到的值和预期值都不一样,就会无限循环导致饥饿,使用 CAS线程数不要超过 CPU 的核心数,采用分段 CAS 和自动迁移机制
    • 解决:如果 JVM 能支持处理器提供的 pause 指令那么效率会有一定的提升,pause 指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使 CPU 不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起 CPU 流水线被清空(CPU pipelineflush),从而提高 CPU 的执行效率
  3. 只能保证一个共享变量的原子操作,
    • 解决:对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁(synchronized)
    • 或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量 i=2,j=a,合并一下 ij=2a,然后用 CAS 来操作 ij
    • 从 Java1.5开始 JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值