Synchronized锁升级过程图解

684 阅读5分钟

这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战

在多线程并发编程中synchronized一直是元老级角色,称呼它为重量级锁。对synchronized进行各种优化之后,为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。

一、使用synchronized实现同步的基础

Java中的每一个对象都可以作为锁。具体表现以下:
1)对于普通同步方法,锁是当前实例对象。
2)对于静态同步方法,锁是当前类的Class对象。
3) 对于同步方法块,锁是Synchonized括号里配置的对象。


二、synchonized在JVM里的实现原理

当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。那么锁到底存在哪里?锁里面会存储什么信息?

从JVM规范中可以看到synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的。但是,方法的同步同样可以使用这两个指令来实现。

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。\

08_synchronized底层原理.jpg

08_synchronized底层原理.jpg




三、Java对象头

synchronized用的锁是存在Java对象头里\

image.png

image.png


Java对象头里的Mark Word里默认存储对的HashCode、分代年龄和锁标记位。在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据。\

image.png

image.png


四、锁的升级与对比

为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁和轻量级锁,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态随着竞争情况升级。锁可以升级不能降级,目的是为了提高获得锁和释放锁的效率。


4.1、偏向锁

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
偏向锁升级:当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程再进入和退出同步时不需要进行CAS操作来加锁和解锁,只需要比对一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果是,表示线程已经获得了锁。如果不是,其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还存储线程1的线程ID,那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1不再使用该锁对象,那么将锁状态设置为无锁状态,重新偏向新的线程。\

image.png

image.png


4.2、轻量级锁

轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价大,如果阻塞不久这个锁就被释放,那这个代价有点得不偿失,因此不阻塞这个线程,让它自旋这等待锁释放。
轻量级锁加锁:线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,然后线程尝试使用CAS将对象头中的Mark Work替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

轻量级锁升级:如果自旋时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这时轻量级锁就会膨胀为重量级量,重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

image.png

image.png


自旋会消耗CPU,为了避免无用的自旋,一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的抢锁。


4.3、锁的优缺点对比

image.png

image.png

\