1、synchronized实现的基础
(1)synchronized是基于Java引用类型的对象实现的,使用时需要指定一个引用类型对象作为锁。
(2)当synchronized修饰在方法上时则可以不显式指定对象,修饰静态方法时默认使用的是该类的class对象,修饰在实例方法上时则默认使用当前对象。
(3)synchronized的锁升级是基于锁对象头中的Mark World部分。Mark World的设计十分精妙:
- 在非加锁状态时用于存储对象的哈希码和分代年龄以及锁状态;
- 在加锁状态时原先存储哈希码和分代年龄的部分则会被更新为指向存储哈希码和分代年龄位置指针;
- 在32位虚拟机中,其结构如下,图来自于深入理解虚拟机第三版:

2、锁升级的过程
1、偏向锁
偏向锁是最轻量级的锁,它应用在一个锁对象的生命周期内只会被一个线程获取的场景下,其加锁过程如下:
- 首先获取锁的线程会先检查锁对象Mark World中的偏向模式标志位是否为可偏向,0为不可偏向,会升级到轻量级锁;1为可偏向,分为以下两种情况:
- 锁对象在第一次被线程获取,那么其可偏向状态为1,此时Mark World中没有记录某个线程的ID,当前线程会使用CAS尝试将自己的线程ID记录在Mark World中,成功则直接进入同步代码块中执行。
- 锁对象不是第一次被线程获取时,线程会检查Mark World中的线程ID是否是自己的,如果是则说明没有第二个线程获取过该锁,直接进入同步代码块执行;如果不是那么说明可能是一个多线程环境,需要撤销偏向模式,也即将标志位改为0,并升级到轻量级锁。
2、轻量级锁
轻量级锁应用在同步代码块执行期间,只会有一个线程去获取锁的场景下,加锁过程如下:
- 线程会在栈帧中保存Mark World的副本,称为锁记录(Lock Record)。然后使用CAS尝试将Mark World更新为指向锁记录的指针:
1.1 若CAS成功则说明没有其他线程竞争锁,则直接进入同步代码块执行。
1.2 若CAS失败则可以分成两种情况:
(1)当前线程重入了该锁,也就是说此时的Mark World是指向当前线程栈帧Lock Record的指针,而栈帧中的Lock Record则是锁对象原本的Mark World的内容,二者当然不同,CAS会失败。但是当前线程是持有该轻量级锁的,因此JVM需要在CAS失败后检查Mark World是否是指向当前线程的指针,如果是,则直接进入同步代码块执行;如果不是,那么就是下面的情况。
(2)这是一个多线程环境,此时的Mark World是指向别的线程Lock Record的指针,需要升级到重量级锁,需要将Mark World更新为指向重量级锁的指针,此后获取该锁的线程会阻塞。 - 轻量级锁会有一个锁释放的过程,当拥有轻量级锁的线程执行完同步代码之后,会使用CAS将栈帧中的锁记录替换回去,如果成功则说明同步代码执行过程中,没有其他线程获取过该锁,同步完成。
- 如果锁释放失败,那么说明有其他线程获取过该锁,此时Mark World是指向重量级锁的指针,已经升级到重量级锁,需要唤醒其他被阻塞的线程。
3、重量级锁
重量级锁是阻塞同步,不占有锁的线程需要陷入阻塞。由于主流JVM使用的是操作系统的原生内核线程,对内核线程的阻塞、唤醒都需要操作系统内核的支持。
陷入内核态需要先保存当前线程的运行环境,然后再切换到内核的运行环境,这是一个十分耗费时间的过程,有时候甚至超过了同步代码本身用时,是一个十分重量的操作,因此得名重量级锁。
4、轻量级锁和偏向锁的区别
- 偏向锁是在一个极端的环境下才会应用的,也即一个锁对象从诞生到死亡都只会有一个线程获取它。如果有第二个线程获取那么则会结束偏向,也就是说偏向锁只能被一个线程获取,且只在第一次获取时进行CAS操作
- 轻量级锁则是应用在同时只会有一个线程执行同步代码的情况下,在轻量级锁被释放后是可以被其他线程获取的,如果轻量级锁的拥有者在执行同步代码的过程之中又有其他线程申请该锁,轻量级锁才会失效。轻量级锁每次申请都需要进行CAS,释放时也需要CAS
5、哈希码和分代年龄的去向
在轻量级锁和重量级锁的应用中,指针会占用原本哈希码和对象分代年龄的位置,为了保证这两个数据不消失,需要另外的地方来进行保存
- 在轻量级锁中,栈帧的锁记录是Mark World的副本,此时这两个数据保存在锁记录中。
- 重量级锁中,指针指向的重量级锁对象是一个ObjectMonitor类对象,每个Object对象都会关联一个该对象,用于作为锁。ObjectMonitor类对象有一个字段用于可以保存Mark World,自然可以保存上述两个数据。
而在偏向锁应用中,线程ID会占去原本哈希码的位置。而只有收到一致性哈希码请求时,如调用Object的hashCode方法(用户重写的不会进行一致性哈希码请求),在第一次计算过后将哈希码存储在该位置。
因此计算过一致性哈希码的对象无法进入偏向模式,而如果偏向过程中收到了该请求则需要撤销偏向状态,直接膨胀到重量级锁。
585

被折叠的 条评论
为什么被折叠?



