本文主要讲解了JVM锁的原理以及锁升级的流程
对象锁
对象锁分为无锁、偏向锁、轻量级锁、重量级锁,且 JVM 的 synchronized 实现可根据场景依次进行锁升级。
Lock Record的实现:
1 | /** |
monitor的实现:
1 | ObjectMonitor::ObjectMonitor() { |
1 偏向锁
1.1 偏向锁流程
1)线程1获取锁,通过 CAS 将对象的 MarkWord 替换为线程1的 id,并在线程栈中添加一条 DisplacedMarkWord 为 null 的 LockRecord。
2)线程1再次获取锁的时候,CAS 失败,此时通过 线程id 判断出是同一个线程,则再次添加一条 DisplacedMarkWord 为 null 的 LockRecord。
3)线程1退出一次锁,从最低位开始循环找到该对象的一个 LockRecord 并删除。
4)线程2获取锁,CAS失败。从虚拟机中遍历所有线程,判断该线程是否存活。如果已经消失,则执行偏向锁的重偏向或撤销(可重偏向替换为匿名偏向锁模式,否则替换为无锁模式执行后续的锁升级)。
5)如果线程1存在,==从低位到高位==遍历线程的 LockRecord。获取最高位的 LockRecord(此时该锁为最早加锁的那一个)。
6)LockRecord 不存在,执行偏向锁的重偏向或撤销。LockRecord 存在,则将该 LockRecord 的 DisplacedMarkWord 替换为对象的 MarkWord 并升级为轻量级锁。
1.2 批量重偏向和批量锁撤销
批量重偏向:当一个线程创建了大量对象并执行了初始的同步操作,后来另一个线程也来将这些对象作为锁对象进行操作,会导偏向锁重偏向的操作。默认偏向锁批量重偏向阈值为:20(BiasedLockingBulkRebiasThreshold)
批量撤销:在多线程竞争剧烈的情况下,使用偏向锁将会降低效率,于是乎产生了批量撤销机制。默认偏向锁批量撤销阈值:40(BiasedLockingBulkRevokeThreshold)
重偏向只会发生在批量重偏向中,且在一个计数周期只会重偏向一次。
1.3 偏向锁注意事项
1)对象存在两种 MarkWord,一个是匿名偏向锁状态(101)和一个无锁状态(001)。前面的 29bit 都为 0。偏向锁在没有偏向所有者的时候,是没有线程 id 的,并且后续所有和偏向锁有关的 cas 都是将 0 替换为自己的线程id 和 Epoch。无锁状态则是因为对象的 hashcode 只有再初次调用的时候才会生成并存储在对象头。
2)开启偏向锁之后对象新建的 MarkWord 都为匿名偏向锁状态。无锁状态加锁会直接膨胀为轻量级锁。
3)通过偏向锁加锁流程我们可以知道,偏向锁记录是没有存储对象的 hashcode,因此偏向锁和 hashcode 是不能共存的。如果对象调用了 hashcode,那么它就不会进入偏向锁而是直接升级为轻量级锁;如果一个对象处于偏向锁状态调用了 hashcode,则会在线程进入安全临界点(safe point)的时候,挂起线程并将偏向锁膨胀为轻量级锁。
4)线程只会删除线程内的锁记录,不会去清除 MarkWord 里面的 线程id。即等其他线程 CAS 失败之后再决定是锁撤销再膨胀还是锁重偏向。
2 轻量级锁
2.1 轻量级流程
1)线程1获取锁,将线程的 MarkWord 复制一份保存在 LockRecord 中,通过 CAS 替换线程的 MarkWord 为该 LockRecord 的指针。
2)线程1再次获取锁的时候,CAS 失败,此时通过 线程id 判断出是同一个线程,则再次添加一条 DisplacedMarkWord 为 null 的 LockRecord。
3)线程1退出一次锁,从最低位开始循环找到该对象的一个 LockRecord 并删除。如果 LockRecord 的 DisplacedMarkWord 为 null,则代表重入锁,直接退出;如果不为 null,则代表没有再持有锁,需要通过 CAS 将对象的 MarkWrod 重新再替换回去。
4)线程2获取锁,CAS失败。根据虚拟机参数来决定是否自旋还是膨胀为重量级锁。
5) 线程1退出锁的时候,CAS 可能失败,因为可能线程2正在将锁膨胀为重量级锁,因此线程1也会进入锁膨胀的方法获取重量级锁。
源码:synchronizer
2.2 轻量级锁膨胀
偏向锁由于 LockRecord 是相同的,所以膨胀方法较简单,只要 copy MarkWord 到 DisplacedMarkWord 中就行,而轻量级锁和重量级锁是两种实现方式。重量级锁由操作系统的 MutexLock(互斥锁) 来实现的,因此操作稍微有点复杂。
调用 omAlloc 分配一个 ObjectMonitor 对象(以下简称monitor),在 omAlloc 方法中会先从线程私有的 monitor 集合 omFreeLis t中分配对象,如果 omFreeList 中已经没有 monitor 对象,则从 JVM 全局的 gFreeList 中分配一批 monitor 到 omFreeList 中。
初始化 monitor 对象。
将状态设置为膨胀中(INFLATING)状态。
设置 monitor 的 header 字段为 DisplacedMarkWord,owner 字段为 LockRecord,obj 字段为锁对象。
设置锁对象头的 markWord 为重量级锁状态,指向第一步分配的monitor对象。
3 重量级锁
简单略