返回

深入剖析 Synchronized 原理和锁膨胀过程 (二)

见解分享

引言

在前一篇博文中,我们介绍了多线程的概念和 Synchronized 的使用。然而,仅仅掌握其用法是不够的。只有深入理解其底层实现,才能在开发过程中游刃有余。因此,本篇博文将深入探究 Synchronized 的实现原理和锁升级(膨胀)的过程。

Synchronized 的原理

Synchronized 是依赖于 Java 虚拟机 (JVM) 中的监视器 (Monitor) 实现的。监视器是一个对象,包含以下数据结构:

  • 锁标志位: 用于表示锁的状态(是否被占用)。
  • 进入数: 记录当前持有该锁的线程数量。
  • 等待队列: 存储等待获取锁的线程。

当一个线程尝试获取锁时,它会首先检查锁标志位。如果锁是空闲的,则线程会直接获得锁,并将进入数加一。如果锁已被占用,则线程会被加入等待队列中。

当持有锁的线程释放锁时,它会将锁标志位重置为未锁定状态,并将进入数减一。如果等待队列中有线程,则会唤醒其中一个线程,允许它尝试获取锁。

锁膨胀过程

在 Java 中,锁可以经历以下几个阶段的膨胀:

1. 无锁: 当一个对象还没有被任何线程锁定时,它处于无锁状态。

2. 偏向锁: 当一个对象被同一个线程多次锁定时,JVM 可能会将该对象升级为偏向锁。偏向锁只允许拥有偏向锁的线程访问该对象,从而提高性能。

3. 轻量级锁: 如果偏向锁不适用于对象(例如,对象被多个线程访问),则对象会升级为轻量级锁。轻量级锁使用 CAS(比较并交换)指令来尝试获取锁,如果失败则会升级为重量级锁。

4. 重量级锁: 如果轻量级锁无法成功获取锁,则对象会升级为重量级锁。重量级锁使用互斥量来管理锁的访问,并允许多个线程同时等待获取锁。

何时会发生锁膨胀

锁膨胀通常发生在以下情况下:

  • 当同一个线程多次访问同一个对象时(偏向锁)。
  • 当多个线程频繁访问同一个对象时(轻量级锁)。
  • 当对象被竞争激烈时(重量级锁)。

锁膨胀的影响

锁膨胀会对程序性能产生负面影响,因为:

  • 偏向锁和轻量级锁的膨胀需要额外的 CPU 指令。
  • 重量级锁会阻塞其他线程,导致等待时间延长。

避免锁膨胀

为了避免锁膨胀,我们可以采取以下措施:

  • 尽量减少对象的锁定时间。
  • 避免在循环或嵌套方法中使用 synchronized。
  • 使用读写锁(ReentrantReadWriteLock)来管理读写操作。
  • 使用原子变量(AtomicInteger、AtomicBoolean)来替换简单的锁操作。

总结

深入理解 Synchronized 的原理和锁膨胀过程对于高效的多线程编程至关重要。通过了解其底层机制,我们可以避免锁膨胀,从而提高程序性能。在下一篇博文中,我们将探讨锁优化的最佳实践,帮助你打造高性能的多线程应用程序。