Java锁升级详解:从无锁到重量级锁的演变

发表时间: 2024-07-07 10:36

锁升级


JDK1.6为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,所以在JDK1.6里锁一共有四种状态,无锁状态,偏向锁状态,轻量级锁状态和重量级锁状态,它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率.

  • Java 中锁有几种状态:无锁 → 偏向锁 → 轻量级锁 → 重量级锁

在 JDK 15 的版本上面,移除了偏向锁了

无锁状态

程序不会有锁的竞争。第一个线程执行完之后,第二个线程再去执行。

偏向锁

偏向锁,字面理解就是说会偏向于第一个访问锁的线程

  1. 当一个线程访问一个同步代码块时,首先会尝试获取偏向锁。
  2. 如果该对象没有被其他线程锁定,并且没有发生过竞争,那么当前线程会将对象头中的标记设置为偏向锁,并将线程ID记录在对象头中。
  3. 当其他线程尝试获取该对象的锁时,会检查对象头中的标记,如果是偏向锁并且线程ID与当前线程ID相同,表示可以获取锁,无需进行同步操作。
  4. 如果其他线程尝试获取该对象的锁时,发现对象头中的标记不是偏向锁或者线程ID不匹配,表示发生了竞争,偏向锁会升级为轻量级锁。

偏向锁的优势

在于减少了同步操作的开销,适用于大部分情况下只有一个线程访问同步代码块的场景。但是如果存在多个线程竞争同一个锁的情况,偏向锁会失效,需要升级为轻量级锁或重量级锁。

一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。

一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了,检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程,如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的)。如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。

轻量级锁

轻量级锁是在 JVM 环境上面

轻量级锁在以下情况下会升级为重量级锁:

  1. 当多个线程竞争同一个锁时,如果自旋等待超过了一定的次数,轻量级锁会升级为重量级锁。
  2. 当一个线程已经持有锁,另一个线程在自旋等待获取锁,而此时又有第三个线程试图访问该锁时,轻量级锁也会升级为重量级锁。

这种升级过程是为了处理更复杂的并发情况,确保线程安全,但也会带来一定的性能开销。因此,在设计并发系统时,需要仔细考虑锁的使用和升级策略,以平衡性能和线程安全的需求。

重量级锁

重量级锁是在操作系统环境上面。重量级锁会阻塞线程。

重量级锁是指当一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。它是依赖于底层操作系统的Mutex实现,也就是互斥锁。重量级锁会让锁从用户态切换到内核态,将线程的调度交给操作系统,因此性能相对较低。

并发编程中,当锁的状态为轻量级锁时,若有另一个线程尝试获取锁但自旋一定次数后仍未成功,那么该锁就会升级为重量级锁,此时其他申请锁的线程会进入阻塞状态,导致性能下降。

总的来说,重量级锁是为了确保线程安全而采取的一种机制,但使用时应注意其带来的性能开销,并尽量通过合理的锁策略来避免不必要的阻塞和性能损失。