Java synchronized
的实现原理与应用
synchronized
是 Java 提供的一种内置关键字,用于实现线程同步,确保多线程环境下对共享资源的互斥访问。它是 JVM 层面的机制,简单易用,但底层实现较为复杂。下面我从实现原理 和应用两个方面详细解释。
1. 实现原理
synchronized
的核心是基于 Monitor(监视器) 机制实现的 Monitor 是对象头(Object Header)中的一部分,包含锁信息。JVM 通过字节码指令和锁优化来保证线程安全。
1.1 基本工作流程
- 加锁(Lock) :当线程执行
synchronized
时,JVM 插入monitorenter
字节码指令。线程尝试获取对象的 Monitor:- 如果 Monitor 空闲,线程持有它(成为 Owner),进入同步块。
- 如果已被其他线程持有,当前线程进入阻塞队列(等待队列),直到 Owner 线程释放。
- 解锁(Unlock) :执行到同步块末尾或异常时,插入
monitorexit
字节码,释放 Monitor,其他等待线程竞争获取。 - 异常处理 :即使发生异常,
monitorexit
也会自动执行(通过异常表机制),确保锁释放。
1.2 锁存储与优化(HotSpot JVM 实现)
synchronized
的锁不是简单的互斥锁,而是通过锁升级机制优化性能,避免不必要的开销。锁信息存储在对象头中(Mark Word,64 位中约占 64 位用于锁状态)。
锁升级过程(从低到高开销):
- 无锁状态:对象未被同步时。
- 偏向锁(Biased Locking):适用于单线程或低竞争场景。首次加锁时,Mark Word 记录线程 ID,后续同一线程无需 CAS(Compare-And-Swap)即可获取锁。竞争时升级。
- 轻量级锁(Lightweight Locking):中等竞争。线程在栈帧中创建锁记录(Lock Record),通过 CAS 更新 Mark Word 指针指向它。失败时自旋重试(自适应自旋,避免立即阻塞)。
- 重量级锁(Heavyweight Locking):高竞争。Mark Word 指向 Monitor 对象(内核态数据结构),线程阻塞/唤醒需 OS 调度(上下文切换,开销大)。
锁类型 | 适用场景 | 存储位置 | 优化机制 | 开销 |
---|---|---|---|---|
偏向锁 | 单线程访问 | Mark Word(线程 ID) | 无 CAS,直接偏向 | 最低 |
轻量级锁 | 低竞争多线程 | Mark Word + 栈锁记录 | CAS + 自旋 | 低 |
重量级锁 | 高竞争 | Mark Word + 内核 Monitor | OS 阻塞/唤醒 | 高 |
- 升级触发 :竞争加剧时自动升级(不可逆)。JVM 参数如
-XX:BiasedLockingStartupDelay=0
可控制偏向锁延迟启用。 - 原子性与可见性 :
synchronized
确保原子性 (操作不可中断)和可见性 (释放锁时刷新缓存,其他线程可见最新值)。底层依赖内存屏障(Memory Barrier)指令(如mfence
)。
1.3 底层字节码示例
编译后字节码:
0: aload_1 // 加载 obj
1: dup
2: astore_2 // 备份 obj
3: monitorenter // 加锁
4: aload_2 // ...
... // 同步代码
N: aload_2
N+1: monitorexit // 解锁
N+2: goto N+5
N+3: astore_2 // 异常时释放锁
N+4: aload_2
N+5: monitorexit
N+6: athrow // 重新抛异常
2. 应用
synchronized
广泛用于线程安全场景,如共享变量、单例模式、线程池等。它简单,但需注意死锁风险(多锁嵌套)。优先用于简单同步;复杂场景可考虑 ReentrantLock
(更灵活)。
2.1 常见应用形式
-
同步方法 :修饰实例方法(锁 this)或静态方法(锁 Class 对象)。
javapublic synchronized void increment() { i++; } // 锁 this public static synchronized void staticIncrement() { i++; } // 锁 Counter.class
-
同步代码块 :粒度更细,指定任意对象作为锁。
javasynchronized (lockObj) { // lockObj 可为任意对象 // 临界区代码 }
2.2 典型应用场景
场景 | 应用方式 | 说明 |
---|---|---|
计数器线程安全 | 同步方法或代码块包裹 i++ |
防止竞态条件(如前文 Counter 示例)。 |
单例模式 | 静态同步方法或双重检查锁定 | 确保实例唯一:synchronized (Singleton.class) { if (instance == null) instance = new ...; } 。 |
生产者-消费者 | 同步块 + wait()/notify() | 锁同一对象,wait() 释放锁,notify() 唤醒。 |
集合线程安全 | 同步包装:Collections.synchronizedList(list) |
内部用 synchronized 包装方法。 |
数据库连接池 | 同步获取/释放连接 | 避免并发超限。 |
2.3 示例:线程安全计数器
java
class SafeCounter {
private int count = 0;
private final Object lock = new Object(); // 专用锁对象
public void increment() {
synchronized (lock) { // 细粒度锁
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
// 多线程测试(类似前文)
public class SyncDemo {
public static void main(String[] args) {
SafeCounter counter = new SafeCounter();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(() -> {
for (int j = 0; j < 10000; j++) {
counter.increment();
}
}));
}
threads.forEach(Thread::start);
threads.forEach(t -> {
try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); }
});
System.out.println(counter.getCount()); // 输出: 100000(精确)
}
}
- 无 synchronized:可能输出 < 100,000(丢失更新)。
- 优势 :简单、无需显式锁管理;劣势:不可中断,性能在高竞争时较低。
3. 注意事项与最佳实践
- 性能 :低竞争用
synchronized
,高竞争考虑ConcurrentHashMap
或Lock
。 - 死锁避免:统一加锁顺序、超时机制。
- Java 版本演进:JDK 6 引入锁优化,提升性能;JDK 8+ 偏向锁默认启用。
- 替代品 :
volatile
(仅可见性)、Lock
接口(可重入、可公平)、JUC 包(高级并发工具)。