面试题:Java 的 synchronized 是怎么实现的?

Java 的 synchronized 关键字提供了一种简单而强大的机制来确保线程安全,它通过隐式的方式实现对象或类级别的锁控制。以下是 synchronized 实现机制的详细解释:

1. 锁的对象

  • 实例方法:当 synchronized 用于实例方法时,它锁定的是调用该方法的对象实例(即 this)。
  • 静态方法:当 synchronized 用于静态方法时,它锁定的是该类的 Class 对象。
  • 代码块:当 synchronized 用于代码块时,可以指定任意对象作为锁。

2. 锁的升级过程

在 Java 中,锁可以通过不同的状态进行优化,这个过程通常被称为锁的膨胀(Lock Escalation)。锁的状态主要包括以下几种:

  • 无锁状态
  • 偏向锁(Biased Locking):适用于单线程访问同步块的情况,默认情况下是开启的。一旦某个线程获取了偏向锁后,后续对该锁的请求将不需要执行任何同步操作,直到有其他线程尝试获取该锁为止。
  • 轻量级锁(Lightweight Locking):如果存在多个线程交替访问同一把锁但不存在实际竞争时,会使用 CAS(Compare And Swap)操作来尝试原子地更新对象头中的指针指向当前线程的栈帧中创建的锁记录,从而避免进入更重的锁状态。
  • 重量级锁(Inflated Locking):当有多个线程同时竞争同一把锁时,锁会被升级为重量级锁,此时 JVM 将依赖于操作系统的互斥量(mutex)实现来管理锁,这涉及到线程挂起和恢复等高成本的操作。

3. 监视器(Monitor)

synchronized 实际上是基于监视器锁(Monitor)实现的。每个 Java 对象都有一个与之关联的监视器锁,这使得它可以被用来作为同步语句的一部分。

当一个线程进入 synchronized 方法或代码块时,它首先检查是否能够获得相应的监视器锁;如果不能,则该线程会被阻塞,直到持有锁的线程释放锁为止。

  • 进入同步区域:当线程试图进入同步块或方法时,JVM 首先检查对象头的 Mark Word 是否已经指向了当前线程的锁记录。如果没有冲突,则设置标志位并继续执行;如果有冲突,则根据当前锁的状态采取相应的措施(如自旋等待、挂起等)。
  • 退出同步区域:当线程离开同步块或方法时,它会释放持有的监视器锁,并通知可能正在等待该锁的其他线程。

4. 自适应自旋

为了减少线程切换带来的开销,JVM 引入了自适应自旋的概念。当一个线程在尝试获取已经被占用的锁时,不会立即进入阻塞状态,而是会在一段时间内不断尝试获取锁(即“自旋”)。

如果在这段时间内成功获得了锁,则避免了上下文切换的成本。自旋次数由 JVM 根据历史经验动态调整。

5. 内存屏障

为了保证内存可见性和有序性,synchronized 还引入了内存屏障的概念。

具体来说,在进入同步块之前插入读屏障,在退出同步块之后插入写屏障,以确保所有对共享变量的修改都能及时刷新到主内存,并且其他线程可以看到这些变化。

综上所述,synchronized 是通过对象头中的标记字段(Mark Word)、监视器锁(Monitor)以及一系列复杂的优化策略(如偏向锁、轻量级锁、重量级锁转换、自适应自旋等)共同作用来实现高效且可靠的线程同步机制。

随着 JVM 不断发展,其性能也得到了显著提升。

THE END
喜欢就支持一下吧
点赞7 分享