synchronized锁升级 —— 偏向锁
从 JDK 15
开始,这一特性被官方标记为废弃状态,如果还想继续使用的话需要通过 JVM
参数手动启用。
参考:https://zhuanlan.zhihu.com/p/365454004
偏向锁使用的前提:
- JDK1.6版本之后且开启了偏向锁配置。偏向锁在JDK6和JDK7中是默认开启的,但是它在应用程序启动几秒后才会激活,如有必要可以使用JVM参数
-XX:BiasedLockingStartupDelay=0
来关闭延迟。如果确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数-XX:UseBiasedLocking=false
关闭偏向锁,那么程序默认会进入轻量级锁状态。
- 被加锁的对象没有真正或者隐式调用父类
Object#hashcode()
方法
代码示例:
引入依赖:
> ><dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> ></dependency>
|
public class SyncLockFlag { static final MyObj obj = new MyObj();
public static void main(String[] args) { System.out.println(">>>>>>>> 无锁"); System.out.println(ClassLayout.parseInstance(obj).toPrintable()); synchronized (obj) { System.out.println(">>>>>>>> 偏向锁"); System.out.println(ClassLayout.parseInstance(obj).toPrintable()); } }
static class MyObj {
} }
|
输出结果:
>>>>>>>> 无锁 com.juzi.juc.sync.SyncLockFlag$MyObj object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 18 0a 06 00 (00011000 00001010 00000110 00000000) (395800) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
>>>>>>>> 偏向锁 com.juzi.juc.sync.SyncLockFlag$MyObj object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 30 6a 5f 6d (00110000 01101010 01011111 01101101) (1834969648) 4 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 8 4 (object header) 18 0a 06 00 (00011000 00001010 00000110 00000000) (395800) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
|
对比这张图:
com.juzi.juc.sync.SyncLockFlag$MyObj object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
|
001
:无锁状态,这只是一个单纯地无锁状态。
com.juzi.juc.sync.SyncLockFlag$MyObj object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 30 6a 5f 6d (00110000 01101010 01011111 01101101) (1834969648)
|
00
:偏向锁状态,因为偏向锁启动有延迟。
加上JVM参数配置后,再跑一次结果:
>>>>>>>> 无锁 com.juzi.juc.sync.SyncLockFlag$MyObj object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 18 0a 06 00 (00011000 00001010 00000110 00000000) (395800) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
>>>>>>>> 偏向锁 com.juzi.juc.sync.SyncLockFlag$MyObj object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 88 80 4e (00000101 10001000 10000000 01001110) (1317046277) 4 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 8 4 (object header) 18 0a 06 00 (00011000 00001010 00000110 00000000) (395800) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
|
无锁 -
101
:轻量级锁,当开启了偏向锁并且没有延迟开启后,新创建对象的mark
word默认就是偏向锁状态。只不过是此时因为没有线程争抢,除了锁标志位和是否为偏向锁标志位外,其余的位数都是0。=>
此时是 未偏向线程的偏向锁。
偏向锁 -
101
:除了101之外,还有其他的信息,即记录了线程ID、epoch、对象分代年龄信息。
修改代码:
public static void main(String[] args) { System.out.println(">>>>>>>> 未偏向线程的偏向锁"); System.out.println(ClassLayout.parseInstance(obj).toPrintable()); obj.hashCode(); synchronized (obj) { System.out.println(">>>>>>>> 偏向锁?"); System.out.println(ClassLayout.parseInstance(obj).toPrintable()); } }
|
输出结果:
>>>>>>>> 未偏向线程的偏向锁 com.juzi.juc.sync.SyncLockFlag$MyObj object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) 18 0a 06 00 (00011000 00001010 00000110 00000000) (395800) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
>>>>>>>> 偏向锁? com.juzi.juc.sync.SyncLockFlag$MyObj object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 30 6a 8f 6b (00110000 01101010 10001111 01101011) (1804560944) 4 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 8 4 (object header) 18 0a 06 00 (00011000 00001010 00000110 00000000) (395800) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
|
可以看到,在第二次输出中为00
,轻量级锁状态。
之前提过偏向锁的前提之一就是:被加锁的对象没有真正或者隐式调用父类Object#hashcode()
方法。
如果一旦调用Object#hashcode()
方法,那么对象头中就有对象的hashcode值。如果偏向锁来进行markword替换,至少需要提供一个保存hashcode的位置,但是偏向锁并没有位置进行markword的保存,轻量级锁有display mark word
。
隐式调用说明:间接调用了hashcode()
方法,比如HashMap#put()
、重写了的equals()
等等。
为了让线程获得锁的代价更低,引入了偏向锁。当前一个线程访问同步块并获取锁时,会在①对象头和②栈帧的锁记录中存储锁偏向的线程ID,之后该线程在进入和退出同步块时不需要使用CAS来加锁和解锁,只需要③判断对象头的Mark
Word中是否存储着指向当前线程的偏向锁。如果匹配成功,表示该线程获取了锁;如果匹配失败,则需要再判断Mark
Word中的偏向锁的表示是否设置成1(表示当前是偏向锁),如果没有设置,则④使用CAS竞争锁;如果设置了,则尝试使用CAS将⑤对象头的偏向锁指向当前线程。
- ①:存储线程ID
- ②:线程栈帧的
LOCK RECORD
存储当前线程ID
- ③:线程ID是否匹配
- ④:认为是无锁状态,去竞争偏向锁
- ⑤:本质上是CAS竞争替换线程ID
资料:https://www.bilibili.com/video/BV1Kb4y1u7vo/ 强推 河北王校长