Z
ZHANK
多线程

同步与锁

理解 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

场景synchronizedLock
简单互斥✅ 最简单啰嗦
尝试获取(不等)✅ tryLock
等待可中断✅ lockInterruptibly
公平锁(先来后到)✅ new ReentrantLock(true)
多个条件变量✅ Condition
自动释放❌ 必须 finally unlock

💡 原则: 优先用 synchronized(简单、安全),需要高级特性时再用 Lock。