synchronized关键字的底层原理
基本使用回顾
Synchronized【对象锁】采用 互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住
Monitor
javap -v xx.class
查看class字节码信息
- Owner:存储当前获取锁的线程的,只能有一个线程可以获取
- EntryList:关联 没有抢到锁的线程,处于
Blocked
状态的线程 - WaitSet:关联调用了wait方法的线程 ,处于
Waiting
状态的线程
synchronized关键字的底层原理-进阶
Monitor
实现的锁属于重量级锁,你了解过锁升级吗?
- Monitor实现的锁属于 重量级锁,里面涉及到了 用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
- 在JDK 1.6引入了两种新型锁机制: 偏向锁和轻量级锁 ,它们的引入是为了解决在 没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。
对象怎么关联上的Monitor
对象的内存结构
在HotSpot虚拟机中,对象在内存中存储的布局可分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充
MarkWord
- hashcode:25位的对象标识Hash码
- age:对象分代年龄占4位
- biased_lock:偏向锁标识,占1位 ,0表示没有开始偏向锁,1表示开启了偏向锁
- thread:持有偏向锁的线程ID,占23位
- epoch:偏向时间戳,占2位
- ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针,占30位
- ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针,占30位
每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的Mark Word 中就被设置指向 Monitor 对象的指针
轻量级锁
在很多的情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是 没必要 的。因此JVM引入了轻量级锁的概念。
加锁流程
- 在线程
栈
中创建一个Lock Record
,将其 obj字段指向锁对象。 - 通过 CAS 指令将Lock Record的 地址存储在对象头的mark word中,如果对象处于 无锁状态则修改成功,代表该线程获得了轻量级锁。
- 如果是当前线程已经持有该锁了,代表这是一次锁重入。设置 Lock Record第一部分为null,起到了一个重入计数器的作用。
- 如果 CAS修改失败,说明发生了竞争,需要膨胀为重量级锁。
解锁过程
- 遍历线程栈,找到所有 obj字段等于当前锁对象的Lock Record。
- 如果Lock Record的Mark Word为
null
,代表这是一次重入,将obj设置为null后continue。 - 如果Lock Record的 Mark Word不为null,则利用 CAS指令将对象头的mark word恢复成为无锁状态。如果失败则膨胀为重量级锁。
偏向锁
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。 Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现 这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有
评论( 0 )