Synchronized 锁升级过程
自己之前写的一篇 Synchronized 的博客:https://blog.csdn.net/Sherlook_Holmes/article/details/128584655
Synchronized 一开始的性能并不好,是 jdk 6 之后,对 Synchronized 进行了优化,才有了偏向锁、轻量级锁、重量级锁这些东西的,这么做的目的,是为了提高 Synchronized 的性能。
因为重量级锁 Monitor 底层使用的是操作系统的 Mutex Lock,互斥锁在进行释放和获取的时候,需要从用户态转换到内核态,这样系统的开销是很大的。
引入轻量级锁的目的是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统 Mutex Lock(互斥锁)产生的性能消耗。如果使用 Mutex Lock 每次获取锁和释放锁的操作都会带来用户态和内核态的切换,这样系统的性能开销是很大的。
偏向锁
● JDK 15 之后,已经处于一个废弃的状态
● 可能的原因:
○ 实际上,对于性能的提升有限
○ 而且代码的维护,实现太复杂了
偏向锁出现在:
只有一个线程加锁的情况下,一定没有竞争!没有竞争!没有竞争!
如果又来一个线程也想对同一个对象加锁,这个时候,偏向锁就会升级成轻量级锁或者重量级锁
【锁都是从无锁->轻量级锁->重量级锁这样一步步升级】
jdk19 以前,有一个开关 -XX:+UseHeavyMonitors 可以禁掉轻量级锁,此时才是从无锁直接膨胀到重量级锁
但 jdk 19 之后,这个参数目前已经没有了。
加锁后,对象的 MarkWord 里存储持有锁的线程地址(是线程id还是线程地址,后来我意识到怎么叫都无所谓,反正代码里能根据这个值区分出来是不是自己这个线程就可以了)
轻量级锁,是不会自旋的。如果发生了竞争,就会把轻量级锁直接升级成重量级锁。
自旋是发生在,锁已经升级成重量级锁之后,而且线程处在 cxq 这个单向链表,栈结构中的时候,才会自旋尝试获取锁,如果自旋期间,能获取到锁,则避免线程的阻塞了,避免了线程频繁的上下文切换,造成的性能开销;如果自旋若干次,还是不能获取到锁,那么线程就进入阻塞状态,由将来持锁线程唤醒。
轻量级锁出现在:
有两个或多个线程试图对同一个对象交替加锁的情况下,都是等一个线程加锁,解锁完成了,另一个线程才加锁,解锁的,没有竞争。
如果发生了竞争,就要升级成重量级锁
为什么只要一有竞争,就要把轻量级锁升级成重量级锁啊?
因为轻量级锁结构简单,不会考虑发生竞争后线程该到哪里阻塞,包括 wait,notify 机制。而重量级锁会使用 Monitor 对象,它功能完备,上面提到的东西都有。
竞争发生,肯定有一个线程会失败,失败后到哪里阻塞呢?轻量级锁不记录这些信息啊
加锁后,对象的 MarkWord 里存储栈帧中的锁记录地址,通过 CAS 来替换的
重量级锁出现在:
有两个或多个线程都试图对同一个对象加锁的情况下,并且发生了竞争
加锁后,对象的 MarkWord 里存储 Monitor 的地址