整体过程:无锁->偏向锁->轻量级锁->重量级锁
1.无锁:加锁对象被实例化,但还未有线程调用,此时对象头中的markword记录的是hashcode、锁标识等该对象的初始信息。
2.偏向锁:
2.1 初次获取:当线程A获取锁时,根据当前加锁对象锁标识进行不同的操作,如果是无锁,进行CAS将线程ID写入markword替换hashcode,写入成功,则表示线程获取锁成功,代码执行完毕释放锁后,也不会将markword内容恢复。
2.2 再次获取:线程A再次尝试获取,但markword内容已经被修改为线程ID,直接获取,不需要再进行set操作。
3.轻量级锁:
3.1 epoch不相等:该锁已经被A使用,且markword信息已被修改为A线程id,此时线程B来竞争锁,线程id没匹配上,但是该加锁对象的epoch和类的epoch也不相等,代表加锁对象不是最新版本,线程B可以CAS获取而不进行锁升级。
3.2 epoch相等:需要JVM介入,发生锁撤销(该加锁对象的markword被还原为无锁状态),在一个安全点(Stop The World)进行检查线程A是否完全释放资源,如果没有释放,升级为轻量级锁,jvm替A在栈上创建一条lockrecord,线程A执行结束后释放锁,线程B通过CAS来竞争锁;如果释放了,则线程B在栈创建lockrecord,通过CAS将这条lockrecord写入到加锁对象的markword中,同时将无锁的markword信息记录到lockrecord的displaced markword中,还把加锁对象的引用也存到了lockrecord中(用于释放锁时还原无锁状态)。
3.3 批量重偏向:假设一个类有100个加锁对象,JVM会记录这100个对象发生锁撤销的次数,当达到阈值时(20),使类的epoch+1(版本更新了,属于线程A的加锁对象是老版本),导致类的epoch和加锁对象的epoch不相等,这样线程B依然通过一次CAS就获取到了加锁对象,而不用再等升级成轻量级锁。(达到阈值的20个已经升级成轻量级锁不可逆,剩余的80个因epoch更新而直接CAS继续维持偏向锁状态)。
4.重量级锁:线程B尝试CAS获取被线程A持有的加锁对象,没成功,发生自旋(while true里一直CAS),当自旋达到阈值后,升级成重量级锁,引入monitor资源监视器,上升到了操作系统级别,线程B进入阻塞状态进入等待池,等线程A释放资源后唤醒线程B。
4.1 锁池:自旋失败后,进入锁池等待锁释放,等待被唤醒,锁池内线程优先级比等待池要高。
4.2 等待池:线程A持有锁,但是需要等其他线程的资源,所以调用wait()使其释放锁(sleep()方法不会释放锁!),由于在JVM看来是主动释放锁,优先级在锁池之后。