synchronized
升级过程
-
无锁–>偏向锁–>轻量级锁–>重量级锁(https://blog.csdn.net/weixin_43899792/article/details/124634419)
-
无锁
前25位不使用,接着31位表示hashCode(如果有调用则显示具体的哈希值,否则默认为0),接着1位不使用,接着4位表示分代年龄(因不存在GC,故为0),下1位标识偏向锁(0),最后2位是锁标志位(01) 故最后3位001表示无锁
-
偏向锁
偏向锁在JDK1.6之后是默认开启的,但是启动时间有延迟,所以需要添加参数
-XX:BiasedLockingStartupDelay=0,让其在程序启动时立刻启动。
MarkWord64位如下(倒着看):
00000101 10100000 10001111 00011011
00000000 00000000 00000000 00000000
前54位表示当前线程指针,最后3位101表示偏向锁
锁第一次被拥有的时候记录线程ID,后面每次同步 都去检查下当前线程ID和锁的偏向锁ID是不是一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
假如不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。
-
偏向锁撤销
一旦有两个线程来竞争锁的时候,就会撤销偏向锁。
全局安全点,这个点所有的线程都会被停下来。
-
轻量级锁
轻量级锁是相对于传统的monitor重量级锁而言的,轻量级锁不能用来代替重量级锁。它只是在一定的情况下减少消耗。
轻量级锁的目的:在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗。但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀为重量级锁。
当关闭偏向锁功能或多线程竞争偏向锁会导致偏向锁升级为轻量级锁。
假如线程A已经拿到锁,这时线程B又来抢该对象的锁,由于该对象的锁已经被线程A拿到,当前该锁已是偏向锁了。而线程B在争抢时发现对象头MarkWord中的线程ID不是线程B自己的线程ID(而是线程A),那线程B就会进行CAS操作希望能获得锁。 此时线程B操作中有两种情况:如果锁获取成功,直接替换Mark Word中的线程ID为B自己的ID(A → B),重新偏向于其他线程(即将偏向锁交给其他线程,相当于当前线程"被"释放了锁),该锁会保持偏向锁状态,A线程Over,B线程上位;如果锁获取失败,则偏向锁升级为轻量级锁,此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程B会进入自旋等待获得该轻量级锁
重量级锁
MarkWord64位如下(倒着看):00001010 11111111 01111000 00011010
00000000 00000000 00000000 00000000
前62位表示指向互斥量(重量级锁)的指针,最后2位10表示重量级锁
Synchronized锁升级过程总结:先自旋,不行再阻塞。实际上是把之前的悲观锁(重量级锁)变成在一定条件下使用偏向锁以及使用轻量级(自旋锁CAS)的形式。
Synchronized在修饰方法和代码块在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的。JDK1.6之前synchronized使用的是重量级锁,JDK1.6之后进行了优化,拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程,而不是无论什么情况都使用重量级锁。 偏向锁: 适用于单线程适用的情况,在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁。 轻量级锁:适用于竞争较不激烈的情况(这和乐观锁的使用范围类似), 存在竞争时升级为轻量级锁,轻量级锁采用的是自旋锁,如果同步方法/代码块执行时间很短的话,采用轻量级锁虽然会占用cpu资源但是相对比使用重量级锁还是更高效。 重量级锁:适用于竞争激烈的情况,如果同步方法/代码块执行时间很长,那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁更严重,这时候就需要升级为重量级锁。 锁的优缺点对比
使用轻量级锁自旋带来的性能消耗就比使用重量级锁更严重,这时候就需要升级为重量级锁。
锁的优缺点对比