一、synchronized 的核心作用
synchronized 是 Java 中实现线程同步的内置锁机制,提供以下保障:
- 原子性:同一时刻仅一个线程能执行同步代码块。
- 可见性:锁释放时,所有共享变量的修改对其他线程可见。
- 有序性:同步代码块内的指令不会被重排序到块外。
二、synchronized 的三种使用方式
1. 修饰实例方法
- 锁对象:当前实例对象(
this)。 - 适用场景:保护非静态变量的并发访问。
public class Counter {
private int count = 0;
public synchronized void increment() {
count++; // 原子操作
}
}
2. 修饰静态方法
- 锁对象:类的
Class对象(如Counter.class)。 - 适用场景:保护静态变量或类级别操作。
public class Logger {
private static int logCount = 0;
public static synchronized void log() {
logCount++; // 原子操作
}
}
3. 修饰代码块
- 锁对象:显式指定的任意对象(通常使用
this或私有锁对象)。 - 适用场景:减少锁粒度,提升并发性能。
public class Cache {
private final Object lock = new Object();
private Map<String, String> data = new HashMap<>();
public void put(String key, String value) {
synchronized (lock) { // 锁定私有对象,避免暴露锁
data.put(key, value);
}
}
}
三、synchronized 的实现原理
1. 对象头与 Monitor
-
对象头结构:每个 Java 对象在内存中包含一个对象头,其中
Mark Word字段存储锁状态信息。- 无锁状态:存储哈希码、分代年龄等。
- 偏向锁/轻量级锁/重量级锁:存储指向 Monitor 的指针或线程 ID。
-
Monitor(监视器锁) :
- 每个对象关联一个 Monitor,通过
ObjectMonitor实现(C++ 结构)。 - 包含
_owner(持有锁的线程)、_EntryList(竞争锁的线程队列)、_WaitSet(等待队列)等字段。
- 每个对象关联一个 Monitor,通过
2. 锁升级过程
为提高性能,JVM 对 synchronized 锁进行优化,经历以下阶段:
-
偏向锁:
- 适用场景:单线程重复访问同步块。
- 机制:在对象头记录线程 ID,避免 CAS 操作。
- 升级条件:检测到其他线程竞争时(通过
epoch机制)。
-
轻量级锁(自旋锁) :
- 适用场景:多线程竞争不激烈(短时间等待)。
- 机制:线程通过 CAS 将对象头替换为锁记录指针,失败后自旋重试。
- 升级条件:自旋超过阈值(默认 10 次)或竞争加剧。
-
重量级锁:
- 适用场景:高并发竞争。
- 机制:线程进入阻塞状态,依赖操作系统 Mutex Lock 调度。
- 缺点:上下文切换开销大。
四、synchronized 与 JMM 的关系
1. 可见性保障
- 锁释放(unlock) : 线程退出同步块时,强制将工作内存的修改刷新到主内存。
- 锁获取(lock) : 线程进入同步块时,清空工作内存,从主内存重新加载共享变量。
2. 有序性保障
- as-if-serial 语义:同步块内的代码不会被重排序到块外。
- 内存屏障:插入
LoadLoad、LoadStore、StoreStore、StoreLoad屏障。
五、synchronized 的优缺点
优点
- 简单易用:语法简洁,无需手动释放锁。
- JVM 优化:锁升级机制减少性能开销。
- 稳定性:Java 语言原生支持,兼容性好。
缺点
- 不可中断:线程等待锁时无法响应中断(
InterruptedException)。 - 灵活性不足:不支持尝试获取锁(
tryLock)或超时机制。 - 锁粒度固定:若使用不当(如锁住大对象),可能导致性能问题。
六、synchronized 的优化实践
1. 减小锁粒度
- 反例:锁住整个方法或大对象。
- 正例:使用细粒度锁(如
ConcurrentHashMap的分段锁)。
public class FineGrainedLock {
private final Object[] locks;
private int[] data;
public FineGrainedLock(int size) {
locks = new Object[size];
data = new int[size];
for (int i = 0; i < size; i++) {
locks[i] = new Object();
}
}
public void update(int index, int value) {
synchronized (locks[index]) { // 仅锁定特定段
data[index] = value;
}
}
}
2. 避免死锁
- 按顺序加锁:统一锁的获取顺序。
- 超时回退:结合
try-catch和wait(timeout)实现。
public void transfer(Account from, Account to, int amount) {
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
// 按哈希值顺序加锁
if (fromHash < toHash) {
synchronized (from) {
synchronized (to) {
from.withdraw(amount);
to.deposit(amount);
}
}
} else if (fromHash > toHash) {
synchronized (to) {
synchronized (from) {
from.withdraw(amount);
to.deposit(amount);
}
}
} else {
// 哈希冲突时使用额外锁
synchronized (tieLock) {
synchronized (from) {
synchronized (to) {
from.withdraw(amount);
to.deposit(amount);
}
}
}
}
}
七、synchronized 与其他同步工具对比
| 特性 | synchronized | ReentrantLock(基于 AQS) | volatile |
|---|---|---|---|
| 锁类型 | 内置锁 | 显式锁 | 无锁,仅内存语义 |
| 中断支持 | 不支持 | 支持 lockInterruptibly() | 不适用 |
| 尝试获取锁 | 不支持 | 支持 tryLock() | 不适用 |
| 公平性 | 非公平 | 支持公平/非公平 | 不适用 |
| 性能 | JVM 优化后接近显式锁 | 灵活但需手动管理 | 轻量级 |
| 适用场景 | 简单同步需求 | 复杂同步控制(如条件变量) | 状态标志、DCL 单例 |
八、总结
synchronized 是 Java 并发编程的基石,通过内置锁机制保障原子性、可见性和有序性。其锁升级优化(偏向锁 → 轻量级锁 → 重量级锁)在多数场景下平衡了性能与安全性。 最佳实践:
- 优先使用
synchronized处理简单同步需求。 - 结合锁粒度优化和顺序加锁避免死锁。
- 在需要高级功能(如超时、中断)时,选择
ReentrantLock。
理解其底层原理(如对象头、Monitor)和 JMM 内存语义,有助于编写高效、线程安全的并发代码。