同步与锁
理解 synchronized、ReentrantLock、volatile、死锁预防
同步与锁
多线程共享数据时,如果两个线程同时修改同一个变量,结果将不可预测——这就是竞态条件。同步机制就是为了保证"同一时刻只有一个线程操作共享数据"。
学完本章你将: 掌握 synchronized、ReentrantLock、volatile 的真正含义、死锁如何产生和避免。
问题:count++ 不是原子操作
java
// 看似一行代码,实际分三步:
// 1. 读 count 的值
// 2. 计算 count + 1
// 3. 写回 count
count++;
// 两个线程同时执行 count++ 1000 次,结果可能小于 2000
// 因为线程 A 读到 10,线程 B 也读到 10,各自 +1 后都写 11——丢失了一次
这就是为什么需要同步。
synchronized —— 互斥锁
synchronized 给一段代码加互斥锁:同一时刻只有一个线程能进入。其他线程在门外排队等。
java
// 锁的是 this 对象
public synchronized void increment() {
count++; // 现在安全了
}
// 锁代码块(更细的粒度,推荐)
public void increment() {
synchronized (this) { // 锁 this
count++;
}
}
// 锁其他对象(不同锁互不影响)
private final Object lockA = new Object();
private final Object lockB = new Object();
synchronized (lockA) { /* 只和用 lockA 的代码互斥 */ }
synchronized (lockB) { /* 和 lockA 无关 */ }
// 静态方法锁的是 Class 对象
public static synchronized void method() { } // 锁 Xxx.class
ReentrantLock —— 可重入锁
比 synchronized 更灵活:支持尝试获取(不一直等)、公平锁(先来后到)、可中断。
java
Lock lock = new ReentrantLock();
lock.lock();
try {
// 临界区
count++;
} finally {
lock.unlock(); // ⚠️ 必须在 finally 中释放!否则死锁
}
// tryLock —— 尝试拿锁,拿不到不傻等
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 拿到锁了
} finally {
lock.unlock();
}
} else {
System.out.println("1秒内没拿到锁,做别的事去了");
}
volatile —— 可见性,不是原子性
CPU 有缓存,线程 A 改了变量,线程 B 不一定马上看到。volatile 保证修改后立即刷新到主内存,读取时从主内存读。但它不保证 count++ 这种复合操作的原子性。
java
class Runner {
private volatile boolean running = true; // 保证可见
public void stop() { running = false; } // 线程 A 改为 false
public void run() {
while (running) { // 线程 B 马上能看到 false
// do work
}
}
}
⚠️ volatile 不解决 count++ 问题! count++ 是读-改-写三步,volatile 只保证每次读/写都从主内存来,但两个线程仍然可能同时读到同一个值。count++ 还是需要 synchronized 或 AtomicInteger。
死锁 —— 互相等待,永远卡住
线程 A:拿了锁1,在等锁2
线程 B:拿了锁2,在等锁1
→ 互相等,永远解不开——死锁
java
// ❌ 死锁场景
synchronized (lock1) {
synchronized (lock2) { /* A 持有 lock1 等 lock2 */ }
}
// 同时另一个线程:
synchronized (lock2) {
synchronized (lock1) { /* B 持有 lock2 等 lock1 */ }
}
预防方案:所有线程按相同顺序获取锁。
synchronized vs Lock
| 场景 | synchronized | Lock |
|---|---|---|
| 简单互斥 | ✅ 最简单 | 啰嗦 |
| 尝试获取(不等) | ❌ | ✅ tryLock |
| 等待可中断 | ❌ | ✅ lockInterruptibly |
| 公平锁(先来后到) | ❌ | ✅ new ReentrantLock(true) |
| 多个条件变量 | ❌ | ✅ Condition |
| 自动释放 | ✅ | ❌ 必须 finally unlock |
💡 原则: 优先用 synchronized(简单、安全),需要高级特性时再用 Lock。