乐极生悲对象锁 -- synchronized、volatile与CAS

一:Synchronized

1.1 场景简介

了解JVM对象结构的攻城狮都知道对象头中有个锁状态标志,Synchronized实现基础就是依靠对象头中的锁标志,详情在JVM系列中解释。Synchronized这把重锁JDK1.6进行优化,锁的状态被划分为无锁、偏向锁、轻锁、重锁,锁的的等级只能上升不能下降。使用Synchronized三种场景下的加锁情况:

  • 实例方法默认锁this
  • 静态方法默认锁class对象
  • 代码块自定义锁对象
1.2:偏向锁进阶
  • 偏向锁设计前提:锁不存在过多的竞争,并且总是由同一线程获得锁
  • 开启与关闭设置:设置偏向锁启动后开始时间-XX:BiasedLockingStartupDelay=0,关闭参数-XX:-UseBiasedLocking=false
  • 偏向锁释放时间:当出现锁资源竞争才释放
操作 描述
加锁 检测对象头运行时数据区域是否有指向当前线程偏向锁,成功就获得了锁。失败检查偏向锁标志位是否为1,否就使用CAS竞争,是就尝试使用CAS将对象头锁标志指向当前线程
撤销 其它线程竞争才释放,全局安全点暂停线程。检测线程存活,否将对象头设置为无锁,是遍历偏向对象锁记录,要么偏向其它线程、设置为无锁、标记对象不适合做偏向锁,环形暂停线程

乐极生悲对象锁 -- synchronized、volatile与CAS_第1张图片

二:volatile

volatile目的在于减少锁的开销使用,锁可以保证线程的互斥性可见性,volatile仅仅完成锁的可见性。另外就是volatile可以禁止指令重排序

2.1:可见性语义

多处理器情境下,线程修改后的数据存在于处理器缓存行中未来得及刷新到系统内存,其它处理器缓存行内数据为未更新数据造成线程之间数据可见性问题。如下图所示:
乐极生悲对象锁 -- synchronized、volatile与CAS_第2张图片

2.2:可见性解决方案

添加汇编指令Lock#,该指令具有如下两个作用:

  • 将当前线程所在处理器缓存行的数据写到系统内存
  • 作废所有处理器缓存内存
2.3:禁止指令重排序

各种博客上都在使用单例模式举例,分配内存为初始化返回错误对象的例子确实经典。指令重排序是因为虚拟机优化产生的问题,volatile可以解决

三:CAS

3.1 CAS + volatile = 锁

在这里插入图片描述
乐极生悲对象锁 -- synchronized、volatile与CAS_第3张图片
使用AtomicInteger类addAndGet()分析,value计数属性使用volatile保证可见性,采用CAS操作保证原子性。两者结合实现锁的两个特性,可见与互斥

3.2 ABA版本

首先是ABA问题,内存值为A,线程修改为B后又修改为A,这时候其它线程进行也会被认定为未修改
乐极生悲对象锁 -- synchronized、volatile与CAS_第4张图片
执行结果线程A与线程B都会打印,这就是模仿的ABA问题,那么要解决这个问题可以使用AtomicStampedReference解决
乐极生悲对象锁 -- synchronized、volatile与CAS_第5张图片
该类通过维护Pair内部类解决ABA问题,该内部类中维护了一个reference对象引用以及一个stamp版本标记
乐极生悲对象锁 -- synchronized、volatile与CAS_第6张图片
相比于AtomicInteger的CAS操作增加了一步版本号的比对,这样就可以解决ABA版本号问题

3.3 自旋浪费资源

乐观锁都会存在自旋操作,如果自旋操作不成功那就意味着不停的循环,所以务必记住乐观锁的使用场景是假设锁竞争不激烈的情况下

你可能感兴趣的:(并发编程)