锁的状态
锁的状态一共有四种:无锁、偏向锁、轻量级锁、重量级锁
锁的状态保存在对象的头文件中,以32位JDK为例:
锁状态 |
25 bit |
4bit |
1bit |
2bit |
||
23bit |
2bit |
是否是偏向锁 |
锁标志位 |
|||
轻量级锁 |
指向栈中锁记录的指针 |
00 |
||||
重量级锁 |
指向互斥量(重量级锁)的指针 |
10 |
||||
GC标记 |
空 |
11 |
||||
偏向锁 |
线程ID |
Epoch |
对象分代年龄 |
1 |
01 |
|
无锁 |
对象的hashCode |
对象分代年龄 |
0 |
01 |
||
锁的初始为无所状态,随着锁的竞争,锁的升级过程为: 偏向锁 ---> 轻量级锁--->重量级锁,锁的升级是单向的,不会出现锁的降级
锁升级过程
(初始:无锁状态)
场景一、线程#1 获取偏向锁
1. 线程#1 访问同步块
2. 当前对象头,锁标志位:01,是否是偏向锁:0
3. CAS更新Mark Word,设置偏向锁标志位1,设置线程ID为线程#1的ID
线程#1成功获取偏向锁,偏向锁获取成功后,线程不会主动释放,即使线程执行完毕
场景二、线程#1拥有偏向锁
1. 线程#1 又要访问同步块
2. 判断锁标志位为01,是偏向锁,判断线程ID是线程#1的ID
3. 执行同步代码
场景三、线程#2 竞争偏向锁
1. 发现当前对象头是偏向锁
2. 判断线程ID是否为线程#2的ID:否
3. 判断线程#1是否还存在,不存在--->场景四,存在--->场景五
场景四、线程#2 竞争偏向锁,线程#1 不存在,线程#2 获取偏向锁
1. 设置偏向锁标志位为0
2. 类似场景一,线程#2 获取偏向锁
场景五、线程#2 竞争偏向锁,线程#1 存在,偏向锁升级为轻量级锁
1. 到达全局安全点(在这个时间点上没有字节码正在执行),线程#1挂起
2. 设置锁标志位为00,将线程#1 的mark word复制到线程栈的lock record中,更新mark word,将mark word指向线程#1中monitor record的指针 (升级为轻量级锁)
3. 继续执行线程#1 的代码
4. 线程#2 自旋获取锁对象
场景六、线程#3竞争轻量级锁
1. 拷贝对象头的mark word到线程栈的lock record中
2. CAS操作将mark word更新为指向lock record的指针,并将Lock record里的owner指针指向object mark word,成功--->3,失败--->4
3. 更新成功,线程#3则获取到了锁
4. 更新失败,检查对象的mark word是否指向当前线程的栈帧,如果是则说明已经拥有该对象锁,直接进入同步快执行。否则轻量级锁膨胀为重量级锁,mark word中存储的是指向重量级锁(互斥量)的指针
轻量级锁CAS操作之前堆栈与对象的状态
轻量级锁CAS操作之后堆栈与对象的状态
重量级锁、轻量级锁和偏向锁之间的转换
总结
锁 |
优点 |
缺点 |
适用场景 |
偏向锁 |
加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 |
如果线程间存在锁竞争,会带来额外的锁撤销的消耗。 |
只有一个线程执行同步块 |
轻量级锁 |
竞争的线程不会阻塞,提高了程序的响应速度。 |
如果始终得不到锁竞争的线程使用自旋会消耗CPU。 |
线程交替执行同步块 追求响应时间。 同步块执行速度非常快。 |
重量级锁 |
线程竞争不使用自旋,不会消耗CPU。 |
线程阻塞,响应时间缓慢。 |
追求吞吐量。 同步块执行速度较长。 |