快捷搜索: 王者荣耀 脱发

synchronized锁的升级过程

1 锁标记

synchronized是基于JVM的内置锁Monitor实现的,synchronized关键字在被编译成字节码之后会翻译成monitorenter和monitorexit两条指令,放在同步语句块的起始和结束处。Monitor的实现依赖于底层操作系统的互斥锁的实现,这是一个重量级锁,性能比较低,但是在Java 5之后的版本对synchronized做了优化,锁状态一共变成了四种:无锁、偏向锁、轻量级锁和重量级锁。锁的升级过程是单向的,不能退化,只能是从偏向锁到轻量级锁到重量级锁的过程。记录这几种锁状态的标记是在对象头的Mark Word中(一个对象的结构分为对象头、实际存放的数据和补齐位。而一个对象头中又分为Mark Word标记字段、指向类元数据信息的指针和数组长度(只有数组对象才有)),32位虚拟机的情况如下所示(64位虚拟机Mark Word的结构和32位的差不太多,多了一些没有使用的bit位):

锁状态 25bit 4bit 1bit 2bit 23bit 2bit 是否是偏向锁 锁标志位 无锁 对象的hashcode 对象分代年龄 0 01 偏向锁 线程id Epoch 对象分代年龄 1 01 轻量级锁 指向线程栈中锁记录的指针 00 重量级锁 指向重量级锁Monitor的指针(依赖底层操作系统的互斥量) 10 GC标记 空 11

其中从偏向锁升级到轻量级锁的时候,会复制一份Mark Word到线程栈上去,里面记录了之前偏向锁的信息,而hashcode可以通过对象的hash方法计算出来。


2 锁升级过程

2.1 偏向锁

一开始的时候是无锁状态。然后此时第一个线程进来了,在对象头的Mark Word中看到此时是无锁状态,就把此时的锁升级为偏向锁,并将自己的线程id用CAS的方式赋值到Mark Word中。然后就进入到了该线程的同步块中。

2.2 轻量级锁

如果此时有第二个线程进来,它会去查看当前偏向锁指向的线程id是否是自己,结果发现不是,但是此时还是会CAS去尝试修改线程id指向自己,去赌一下第一个线程此时已经用完了释放了。如果释放了,它会将锁改为无锁状态,将线程id置空。然后第二个线程拿到这个资源,将线程id赋值给自己,锁升级为偏向锁。如果第一个线程此时没释放,则JVM会在第一个线程到达安全点的时候撤销当前的偏向锁。下一步当前线程栈中会分配锁记录,并拷贝Mark Word到锁记录中。然后两个线程用CAS的方式去修改Mark Word中的指针指向自己,假如说第一个线程修改成功了,然后将锁升级为轻量级锁,去执行同步语句块中的内容。

2.3 重量级锁

修改失败的第二个线程会进入自旋状态,自旋结束后会继续去尝试CAS修改指针指向自己。如果自旋失败超过一定次数的时候(这个次数会动态进行调整),会请求JVM将此时的锁状态升级为重量级锁,这是依赖于底层操作系统的调度库来实现的。接着将Mark Word指向重量级锁Monitor的指针,然后挂起当前第二个线程(被放在Monitor的_EntryList中)。等一个线程执行完毕后,会查看当前Mark Word中的指针是否仍然指向自己,如果是自己的话就释放锁,否则不是自己的话,说明此时已经升级成了重量级锁,除了释放锁之后,还会唤醒阻塞的线程,进行新一轮的锁竞争。在此之后,该锁就一直会是重量级锁存在了

经验分享 程序员 微信小程序 职场和发展