前置概念
首先我们要了解到锁的四种状态:无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态。
无锁状态
在无锁状态下,没有线程持有锁。这意味着任何线程都可以自由地访问和修改共享资源,而不需要进行同步或互斥。无锁状态是最理想的情况,因为它不会引入任何额外的开销或阻塞。
偏向锁状态
当只有一个线程访问锁时,可以将锁设为偏向锁。偏向锁的目标是减少线程竞争,提高单线程场景下的性能。在偏向锁状态下,锁会记录当前持有锁的线程的标识。当其他线程访问锁时,它们会检查偏向锁的标识,如果与自己的线程标识匹配,则无需竞争锁,直接进入临界区。这样做可以减少锁的粒度,提高性能。
轻量级锁状态
轻量级锁使用 CAS(Compare and Swap)操作来实现锁的加锁和解锁,避免了传统的互斥量和阻塞机制。在轻量级锁状态下,线程会通过 CAS 操作尝试将锁的状态从偏向锁升级为轻量级锁。
重量级锁状态
重量级锁使用操作系统提供的底层互斥量机制实现,通过阻塞和唤醒线程来实现互斥。在重量级锁状态下,线程会进入阻塞状态,等待锁的释放。当锁被释放时,等待的线程会被唤醒并进行竞争,获取锁后进入临界区。
锁的升级过程
首先当没有现成持有锁时,该对象处于无锁状态。任何线程都可以自由的访问和修改该对象。
无锁->偏向锁
当有一个线程第一次访问处于无锁状态的对象时,JVM会将该对象的锁设置为偏向锁,并将该线程的ID记录在锁的对象头中。这样,当该线程再次访问该对象时,无需进行锁竞争和互斥操作,直接获取偏向锁,从而提高性能。当其他线程尝试访问该对象时,它们会检查偏向锁的标识,如果与自己的线程标识匹配,则无需竞争锁,直接进入临界区。
偏向锁->轻量级锁
当有多个线程尝试获取同一个对象的锁时,偏向锁会失效。当偏向锁失效后,JVM会尝试将锁升级为轻量级锁。此时,它会使用 CAS(Compare and Swap)操作来尝试获取锁。如果其他线程已经获取到了轻量级锁。此时,该现成会进入自旋获取锁的状态等待获取。
轻量级锁->重量级锁
当一个线程尝试获取轻量级锁失败时,JVM会将轻量级锁的状态标记为“膨胀”(Inflated)。
轻量级锁膨胀为重量级锁时,JVM会将对象的锁状态从轻量级锁转换为重量级锁。这个过程涉及到锁对象的内部数据结构的改变,使得锁的状态由线程自己管理转换为由操作系统底层的互斥量来管理。
当一个线程尝试获取重量级锁时,它会使用底层的互斥量(如操作系统提供的锁机制)来实现互斥。如果其他线程已经持有了重量级锁,那么等待的线程将进入阻塞状态,直到持有锁的线程释放锁。一旦锁被释放,等待的线程将被唤醒并进行竞争,获取锁后进入临界区。