synchronized
是 Java 中一种用于保证线程安全的机制,它通过加锁的方式来控制对共享资源的访问。它的实现原理基于 监视器锁(Monitor Lock)和 对象头,通过加锁和解锁来确保同一时刻只有一个线程可以执行被 synchronized
修饰的代码块或方法。
synchronized
实现原理
-
对象头和监视器锁:
- 每个 Java 对象都有一个与之关联的对象头,Java 虚拟机将对象头分为两部分:Mark Word 和 Klass Pointer。Mark Word 存储着对象的哈希码、GC信息、锁状态等。
- 在 Java 中,
synchronized
锁是通过对象的监视器(monitor)来实现的。每个对象都对应一个监视器,监视器的管理由 JVM 提供。通过监视器来控制线程的访问,保证在同一时刻只有一个线程能访问同步代码块或方法。
-
锁的获取与释放:
- 当一个线程访问被
synchronized
修饰的方法或代码块时,首先需要获得对象的锁。若其他线程已经持有该锁,当前线程将进入阻塞状态,直到锁被释放。
- 线程获取锁时,会通过修改对象头中的锁标志位来标记该对象的状态。JVM 使用对象头中的标志位来管理不同的锁状态(如无锁、偏向锁、轻量级锁、重量级锁等)。
-
锁的升级与降级:
- 偏向锁:线程首次获取锁时,JVM 会首先尝试为该锁分配偏向锁,偏向锁会将锁的标志指向当前线程 ID,表示当前线程对该对象拥有锁的偏向。如果线程后续继续访问该对象,则无需再竞争锁。
- 轻量级锁:如果锁已经被其他线程持有,JVM 会尝试通过原子操作(CAS)来获取轻量级锁。轻量级锁通过在对象头中使用 CAS 操作来尝试获取锁,不会阻塞当前线程。轻量级锁是为了减少线程间的竞争,提高性能。
- 重量级锁:当多线程竞争轻量级锁失败,或者轻量级锁升级失败时,JVM 会将锁升级为重量级锁。重量级锁涉及操作系统的原子操作,线程会被阻塞,直到获取锁为止。
-
内存可见性保证:
synchronized
还确保了内存的可见性。当一个线程释放锁时,所有它所做的修改会被刷新到主内存中,其他线程在获得该锁时,能看到这些修改。这样就保证了共享数据的可见性和一致性。
- 同时,
synchronized
还会保证在加锁区域内,所有操作的原子性,避免了多个线程同时修改共享数据的问题。
-
JVM中的锁优化:
- JVM 会根据线程的竞争情况动态地调整锁的实现。例如,
synchronized
使用时,JVM 会使用 自旋锁 来避免频繁的线程阻塞/唤醒操作,减少线程切换的开销。
- 如果某个线程获取锁后执行时间很短,JVM 会尝试通过自旋来减少线程的上下文切换,降低性能损耗。
锁的不同状态:
synchronized
实现的锁具有不同的状态,用来表示锁的竞争情况:
- 无锁状态:对象没有任何线程持有锁,可以被任何线程获取。
- 偏向锁:一个线程持有锁并且没有其他线程竞争时,锁会变为偏向锁。偏向锁的目的是提高性能,减少锁的竞争。
- 轻量级锁:多个线程竞争时,JVM 会通过 CAS(Compare-And-Swap)操作来尝试获取锁,但不会阻塞线程。轻量级锁的目的是减少线程间的竞争,提高性能。
- 重量级锁:当多个线程竞争激烈时,JVM 会将锁提升为重量级锁。此时,线程会进入阻塞状态,直到获取锁。重量级锁涉及操作系统的调度和线程上下文切换,性能较差。