一、轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程访问,但多线程访问的时间错开(也就是没有竞争),那么可以使用轻量级锁来优化。
轻量级锁对使用者是透明的,即语法仍然是synchronized。

假设有两个方法同步块,利用同一个对象加锁

  1. static final Object obj=new Object();
  2. public static void method1(){
  3. synchronized (obj){
  4. //同步块A
  5. method2();
  6. }
  7. }
  8. public static void method2(){
  9. synchronized (obj){
  10. //同步块B
  11. }
  12. }
  • 创建锁记录(Lock Record)对象,不像重量级锁是Monitor对象作为锁,轻量级锁是Lock Record作为轻量级锁,每个线程的栈帧中都会包含一个锁记录的结构(如本例中就是method1方法对应的栈帧中创建Lock Record对象),内部可以存储锁定对象的Mark Word,其中Hashcode Age Bias 01是Object的Mark Word部分,记录对象当前的状态以及是否被加锁,如果是01代表对象是normal状态,即未被加锁。

    image.png

  • 让锁记录中Object reference指向锁对象,并尝试用cas替换Object中的Mark Word,将Mark Word的值存入锁记录。

    image.png

  • 如果cas替换成功,对象头中存储了锁记录地址和状态00,表示由该线程给对象加锁,这时图示如下,00代表该对象被加上轻量级锁。对象Mark Word中的原信息存储在Lock Record中等待解锁的时候恢复。

    image.png

  • cas替换失败有两种情况

    1. -如果其他线程已经持有了该Object的轻量级锁(即发现ObjectMark Word的状态码是00),这时表示有竞争,进入锁膨胀过程。<br /> -如果是自己(当前线程继续对该对象加锁)执行了synchronized锁重入,那么再添加一条Lock Record作为重入的计数。<br /> ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23190196/1641026467201-18a94007-eed6-49bd-ac10-0ae0bb437c3e.png#clientId=u69b67c61-4b00-4&from=paste&height=231&id=ub0d2cf80&margin=%5Bobject%20Object%5D&name=image.png&originHeight=435&originWidth=725&originalType=binary&ratio=1&size=155503&status=done&style=none&taskId=u762ce14b-2894-40ee-b4fc-59544048b38&width=385.49578857421875)<br /> 如本例的情况即为在同一线程内再次synchronized(Obj),会继续对Object上轻量级锁,即在当前线程内再创建一个Lock Record对象,但是Lock Record地址设为null值,仅仅作为重入计数。
  • 当退出synchronized代码块(解锁时)如果有取值为null的锁记录,表示有重入,Lock Record直接清除掉即可。

    image.png

  • 当退出synchronized代码块(解锁时)的记录值不为null,这时使用cas将Mark Word的值恢复给对象头

    1. -成功,则解锁成功<br /> -失败,说明轻量级锁j进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程。

注意:

  • 重量级锁,obj才会和一个monitor对象关联。轻量级锁,是线程内创建的Lock Record与Obj相关联。
  • 每执行一次synchronized,就会在栈帧内创建一个Lock Record锁记录对象,Object reference也会指向要加锁的对象,并尝试cas交换内容,虽然可能会交换不成功,即Object已经被其它线程上锁,但Lock Record依旧会被创建。

二、锁膨胀


如果在尝试加轻量级锁的过程中,CAS操作无法成功,这时代表其它线程在为此对象加上了轻量级锁(有竞争),而轻量级锁是无竞争状态的锁,所以涉及到竞争的情况,需要进行锁膨胀,将轻量级锁膨胀为重量级锁(Monitor锁)

  1. static Object obj = new Object();
  2. public static void main(){
  3. synchronized(obj){
  4. //同步块
  5. }
  6. }
  • 当Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁

    image.png

  • 这时Thread-1加轻量级锁失败,进入锁膨胀流程。注意当Thread-1执行到synchronized(obj)时,依然会尝试为obj加锁,即在线程的方法对应的栈帧中创建Lock Recored对象,Object reference也依旧会指向Object对象,但当执行cas操作时,发现object对象已经被其它线程加上了轻量级锁,此时进入锁膨胀。

    1. <br /> 流程为:<br /> -为object对象申请Monitor锁,让object指向重量级锁地址<br /> -然后自己进入Monitor的EntryList 【BLOCKED】 <br /> ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23190196/1641033799735-07b18fff-8583-4ec4-92fb-f2f61bc145d1.png#clientId=udf8a3c56-952d-4&from=paste&height=238&id=u6059fcad&margin=%5Bobject%20Object%5D&name=image.png&originHeight=430&originWidth=1114&originalType=binary&ratio=1&size=306290&status=done&style=none&taskId=ue5140bfa-81fc-4eec-9434-3c546a0be36&width=616.983154296875)
  • 当Thread-0退出同步块解锁时,由于Thread-0持有的(注意持有的说法,先给obj上锁,然后只有拿到锁才能执行同步代码块的内容)是轻量级锁,所以释放锁时按照轻量级锁释放流程,使用cas将Mark Word的值恢复给对象头,失败。因为此时object中的Mark Word已经不再是Thread-0的Lock Record地址,而是Monitor的地址,所以无法将Hashcode等信息替换到Mark Word字段中。这时则需要进入重量级锁的解锁流程,即按照Monitor的地址找到Monitor对象,然后将Lock Record中的obj的Hashcode Age等信息存入Monitor中,注意这一步是必须的,否则锁释放完毕后无法恢复obj的头信息,最后设置Owner为null,唤醒EntryList中【BLOCKED】线程即可。