synchronized关键字
synchronized 是 Java 最基础的互斥同步机制。它基于对象监视器 Monitor 实现,可以修饰实例方法、静态方法和代码块。
它解决的不只是“同一时间只有一个线程执行”,还包含可见性和有序性语义。
# 1. 三种用法
修饰实例方法:
public synchronized void increment() {
count++;
}
锁对象是当前实例 this。
修饰静态方法:
public static synchronized void refresh() {
}
锁对象是当前类的 Class 对象。
修饰代码块:
synchronized (lock) {
count++;
}
锁对象是括号里的对象。
# 2. 锁对象
| 写法 | 锁对象 |
|---|---|
synchronized instanceMethod | this |
synchronized staticMethod | Class 对象 |
synchronized(lock) | 指定的 lock 对象 |
示意:
线程 A -> synchronized(lock) -> 持有 monitor
线程 B -> synchronized(lock) -> 等待
线程 C -> synchronized(otherLock) -> 不受影响
锁的是对象,不是代码。两个线程只有竞争同一个锁对象时才互斥。
# 3. Monitor 模型
每个 Java 对象都可以关联一个 Monitor。
Object
│
▼
Monitor
├─ Owner:当前持锁线程
├─ EntryList:等待进入锁的线程
└─ WaitSet:调用 wait 后等待通知的线程
进入 synchronized:
尝试获取 monitor
│
├─ 成功:成为 Owner,执行临界区
└─ 失败:进入 EntryList,线程 BLOCKED
退出 synchronized 时释放 monitor,其他等待线程再竞争。
# 4. 可重入性
synchronized 是可重入锁。同一个线程已经持有某个锁时,可以再次获取它。
public synchronized void a() {
b();
}
public synchronized void b() {
}
执行过程:
线程 T 获取 this 锁,进入 a
│
▼
调用 b,再次获取 this 锁
│
▼
重入次数 +1
可重入避免了同一线程调用自身同步方法时死锁。
# 5. synchronized 的内存语义
进入和退出 synchronized 会建立 happens-before 关系。
线程 A:
synchronized(lock) {
shared = 1;
}
线程 B:
synchronized(lock) {
read shared;
}
规则:
线程 A 释放 lock
happens-before
线程 B 随后获取同一个 lock
因此 B 能看到 A 在释放锁前对共享变量的修改。
# 6. 字节码视角
同步代码块通常对应:
monitorenter
临界区
monitorexit
异常时也必须释放锁,因此编译器会生成异常路径上的 monitorexit。
同步方法则通过方法访问标志表达,JVM 在方法调用和返回时处理 monitor。
# 7. 锁粒度
锁粒度过大:
public synchronized void process() {
readFromRemote();
updateState();
writeLog();
}
如果远程调用很慢,其他线程长时间等待。
优化思路:
Data data = readFromRemote();
synchronized (lock) {
updateState(data);
}
writeLog();
但不能为了缩小锁范围破坏共享状态一致性。锁要覆盖完整的不变量维护过程。
# 8. 不要锁字符串和包装类
不推荐:
synchronized ("LOCK") {
}
字符串字面量会进入常量池,可能被其他代码共享。
也不要锁可能被缓存的包装类:
synchronized (Integer.valueOf(1)) {
}
推荐:
private final Object lock = new Object();
# 9. synchronized 与 ReentrantLock
| 对比项 | synchronized | ReentrantLock |
|---|---|---|
| 使用方式 | 关键字 | API |
| 释放锁 | 自动释放 | 必须手动 unlock |
| 可重入 | 支持 | 支持 |
| 可中断获取 | 不支持 | 支持 |
| 尝试获取 | 不支持 | 支持 tryLock |
| 公平锁 | 不支持配置 | 支持 |
| 条件队列 | 一个 WaitSet | 多个 Condition |
简单互斥优先 synchronized;需要可中断、超时、公平锁、多条件队列时考虑 ReentrantLock。
# 10. 死锁风险
synchronized (lockA) {
synchronized (lockB) {
}
}
如果另一个线程反过来先拿 lockB 再拿 lockA,可能死锁。
避免策略:
- 固定锁顺序。
- 减少嵌套锁。
- 使用
tryLock加超时。 - 避免锁内调用外部未知代码。
# Tips 快问快答
Q:synchronized 锁的是代码还是对象?
A:锁的是对象。多个线程竞争同一个锁对象时才互斥。
Q:实例同步方法锁什么?
A:锁当前实例 this。
Q:静态同步方法锁什么?
A:锁当前类的 Class 对象。
Q:synchronized 是可重入的吗?
A:是,同一线程可重复获取同一把锁。
Q:synchronized 能保证可见性吗?
A:能。释放锁 happens-before 后续获取同一锁。
Q:为什么不要锁字符串? A:字符串常量池可能导致锁对象被意外共享。
Q:锁范围越小越好吗? A:不一定,要保证共享状态的不变量被完整保护。
Q:什么时候用 ReentrantLock? A:需要 tryLock、可中断、公平锁或多个条件队列时。