锁升级过程
-
当JVM启动后,一个共享资源对象直到有线程第一个访问时,这段时间内是处于无锁状态,对象头的Markword里偏向锁标识位是0,锁标识位是01。
Tips:当一个共享资源首次被某个线程访问时,锁就会从无锁状态升级到偏向锁状态,偏向锁会在Markword的偏向线程ID里存储当前线程的操作系统线程ID,偏向锁标识位是1,锁标识位是01。此后如果当前线程再次进入临界区域时,只比较这个偏向线程ID即可,这种情况是在只有一个线程访问的情况下,不再需要操作系统的重量级锁来切换上下文,提供程序的访问效率。
-
jdk15版本后默认关闭了偏向锁。如果未开启偏向锁(或者在JVM偏向锁延迟时间之前)有线程访问共享资源则直接由无锁升级为轻量级锁,请看第三步
-
如果未开启偏向锁(或者在JVM偏向锁延迟时间之前),有线程访问共享资源则直接由无锁升级为轻量级锁,开启偏向线程锁后,并且当前共享资源锁已经是偏向锁时,再有第二个线程访问共享资源锁时,此时锁可能升级为轻量级锁,也可能还是偏向锁状态,因为这取决于线程间的竞争情况,如有没有竞争,那么偏向锁的效率更高(因为频繁的锁竞争会导致偏向锁的撤销和升级到轻量级锁),继续保持偏向锁。如果有竞争,则锁状态会从偏向锁升级到轻量级锁,这种情况下轻量级锁效率会更高。
Tips:当第二个线程尝试获取偏向锁失败时,偏向锁会升级为轻量级锁,此时,JVM会使用CAS自旋操作来尝试获取锁,如果成功则进入临界区域,否则升级为重量级锁。
轻量级锁是在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,尝试拷贝锁对象头的Markword到栈帧的Lock Record,若拷贝成功,JVM将使用CAS操作尝试将对象头的Markword更新为指向Lock Record的指针,并将Lock Record里的owner指针指向对象头的Markword。若拷贝失败,若当前只有一个等待线程,则可通过自旋继续尝试, 当自旋超过一定的次数,或者一个线程在持有锁,一个线程在自旋,又有第三个线程来访问时,轻量级锁就会膨胀为重量级锁。
-
轻量级锁升级为重量级锁,此时,JVM会将线程阻塞,直到获取到锁后才能进入临界区域,底层是通过操作系统的mutex lock来实现的,每个对象指向一个monitor对象,这个monitor对象在堆中与锁是关联的,通过monitorenter指令插入到同步代码块在编译后的开始位置,monitorexit指令插入到同步代码块的结束处和异常处,这两个指令配对出现。JVM的线程和操作系统的线程是对应的,重量级锁的Markword里存储的指针是这个monitor对象的地址,操作系统来控制内核态中的线程的阻塞和恢复,从而达到JVM线程的阻塞和恢复,涉及内核态和用户态的切换,影响性能,所以叫重量级锁。