一句话总结
synchronized用于控制多线程对共享资源的访问,可修饰方法或代码块,通过指定锁对象实现互斥。其原理基于对象内部的监视器锁(Monitor),线程进入时获取锁,退出时释放锁,底层依赖操作系统的互斥锁及内存屏障机制保证原子性与可见性。JVM会优化锁状态(偏向锁、轻量级锁等)提升性能。
详细解析
synchronized 是 Java 中用于实现线程同步的关键字,通过内置锁(Monitor)机制保证多线程环境下代码块或方法的互斥访问。以下是其用法及底层原理的详细说明:
一、synchronized 的用法
1. 修饰实例方法
- 锁对象:当前实例对象(this)。
- 作用范围:整个方法体。
- 示例:
public synchronized void instanceMethod() {
// 同步代码
}
2. 修饰静态方法
- 锁对象:类的Class对象(如MyClass.class)。
- 作用范围:整个静态方法体。
- 示例:
public static synchronized void staticMethod() {
// 同步代码
}
3. 修饰代码块
- 锁对象:显式指定的任意对象(通常为共享资源)。
- 作用范围:代码块内部。
- 示例:
private final Object lock = new Object();
public void blockMethod() {
synchronized (lock) {
// 同步代码
}
}
二、synchronized 的原理
1. 底层实现
- 字节码指令:通过monitorenter和monitorexit指令实现锁的获取与释放。
- 对象头结构:每个 Java 对象在内存中分为三部分:
- 对象头(Header):存储锁状态、GC 分代年龄、哈希码等。
- 实例数据(Instance Data):对象的字段值。
- 对齐填充(Padding):补齐内存对齐。
- 对象头中的锁标记:
- Mark Word(32/64 位):存储锁标志位(如无锁、偏向锁、轻量级锁、重量级锁)。
2. 锁升级过程
JDK 1.6 后,synchronized 引入了 锁膨胀 机制,根据竞争情况动态升级锁状态,以平衡性能与开销:
锁状态 | 适用场景 | 实现方式 |
无锁 | 无竞争 | 直接访问资源 |
偏向锁 | 单线程重复访问 | 在 Mark Word 中记录线程 ID |
轻量级锁 | 多线程交替执行,无实际竞争 | CAS 自旋获取锁 |
重量级锁 | 多线程高竞争 | 通过操作系统互斥量(Mutex)实现 |
锁升级流程:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
3. 锁膨胀机制详解
- 偏向锁:
- 目的:减少无竞争时的锁开销。
- 触发条件:锁对象首次被线程访问。
- 实现:Mark Word 记录线程 ID,后续无需 CAS 操作。
- 撤销:当其他线程尝试获取锁时,偏向锁升级为轻量级锁。
- 轻量级锁:
- 目的:减少线程阻塞开销,通过自旋(CAS)竞争锁。
- 触发条件:多个线程交替执行,未发生竞争。
- 实现:将对象头替换为指向线程栈中锁记录的指针。
- 失败处理:自旋超过阈值(默认 10 次)后升级为重量级锁。
- 重量级锁:
- 目的:应对高并发竞争场景。
- 实现:通过操作系统互斥量(Mutex)阻塞线程,进入等待队列。
- 开销:涉及用户态与内核态切换,性能较低。
三、synchronized 的特性
特性 | 说明 |
互斥性 | 同一时间仅允许一个线程持有锁。 |
可见性 | 锁释放前,变量的修改会刷新到主内存;锁获取时,本地缓存失效。 |
可重入性 | 同一线程可重复获取同一把锁(锁计数器递增)。 |
非公平锁 | 默认非公平,允许插队竞争,提高吞吐量。 |
四、示例解析
1. 单例模式(双重检查锁)
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- volatile 作用:防止指令重排序导致未初始化对象被引用。
- synchronized 作用:保证单例创建的原子性。