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

本文详细解析了Java并发中的Synchronized锁升级机制,包括偏向锁、轻量级锁和重量级锁;阐述了volatile关键字如何确保多线程环境下的变量可见性及禁止指令重排序;并介绍了CAS操作结合volatile实现锁的原理,以及ABA问题的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一:Synchronized
1.1 场景简介

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

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

偏向锁加锁流程

二:volatile

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

2.1:可见性语义

多处理器情境下,线程修改后的数据存在于处理器缓存行中未来得及刷新到系统内存,其它处理器缓存行内数据为未更新数据造成线程之间数据可见性问题。如下图所示:
内存示意图

2.2:可见性解决方案

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

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

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

三:CAS
3.1 CAS + volatile = 锁

在这里插入图片描述
在这里插入图片描述
使用AtomicInteger类addAndGet()分析,value计数属性使用volatile保证可见性,采用CAS操作保证原子性。两者结合实现锁的两个特性,可见与互斥

3.2 ABA版本

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

3.3 自旋浪费资源

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值