Java并发编程艺术 - 原子性及CAS使用和原理

原子性

处理器保障从系统内存中读取和写入字节是原子的,即其他处理器不能访问该字节地址。

总线锁保障原子性

处理器发出Lock#信号到总线上,其他处理器阻塞,该处理器独享内存。

缓存锁保障原子性

总线锁把CPU和内存通信锁住,其他CPU无法访问其他内存地址,开销较大。

可以在缓存中,锁定内存地址,保障原子性。

缓存锁定缓存行时,回写内存不声明LOCK#,而是修改内部内存地址,同时通过缓存一致性阻止其他处理器修改;当其他处理器回写
,将会无效缓存行,从内存重新加载数据。

JAVA实现原子操作

通过锁实现原子操作

锁机制保障只有获取锁的线程才能操作内存区域,常见的JVM锁有偏向锁、轻量锁和互斥锁。

除偏向锁外,其他锁实现都采用CAS进行锁的获取和释放。

通过CAS实现原子操作

CAS通过Compare And Swap可以实现原子操作;原子操作即不能被进一步分割的最小粒子。

CAS需要输入两个参数,old值和expect值;若old值未发生变化,则替换old为expect,否则不交换。实际上是三个参数,第三个参数无需输入,可使用局部变量或者由操作指令维护,是old的origin值,用于和输入的old比较。

JVM中的CAS操作正是利用了处理器的CMPXCHG指令实现,自旋CAS实现的就是循环CAS直到成功。

从Java 1.5开始,JDK的JUC包里提供了原子操作,如AtomicBoolean(用原子
方式更新的boolean值)、AtomicInteger(用原子方式更新的int值)和AtomicLong(用原子方式更
新的long值)。这些原子包装类还提供了以原子的方式将当前值自增1和自减1。

CAS产生的问题

ABA问题

假如old和expect值相同,虽然交换了,但结果貌似没有变化,而且也不知道是否真的交换成功。

因此需要引入版本号,假如每次交换后版本加1,则可以明显确定是否交换成功,例如AtomicStampedReference。

长时间自旋

如果长时间CAS自旋失败,则浪费CPU。

可以提供暂停操作,实现自旋延迟效果。

只能支持单个变量的原子性

多个变量原子性操作通常需要用锁,或者采用把多个共享变量合并成一个共享变量。

AtomicReference支持对象的原子性,可以将多个变量放在一个对象中进行CAS。

------ 本文结束------

本文标题:Java并发编程艺术 - 原子性及CAS使用和原理

文章作者:Perkins

发布时间:2019年05月05日

原始链接:https://perkins4j2.github.io/posts/28363/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。