返回首页面试题

面试必备:synchronized 与 ReentrantLock 区别

2026年03月25日8 min read

面试必备:synchronized 与 ReentrantLock 区别

先说结论

特性synchronizedReentrantLock
底层实现JVM 内置关键字JDK 层面 API
锁类型非公平锁公平/非公平可配
等待唤醒Object.wait/notifyCondition.await/signal
可中断支持 tryLock
多条件只能有一个可绑定多个 Condition
释放方式自动释放必须 finally 释放

synchronized 详解

用法

// 1. 修饰代码块(指定锁对象)
public void method() {
    synchronized (this) {
        // 临界区
    }
}

// 2. 修饰实例方法(锁 this)
public synchronized void method() {
    // 临界区
}

// 3. 修饰静态方法(锁类对象)
public synchronized static void staticMethod() {
    // 临界区
}

底层原理(JDK 1.6+)

锁升级过程

无锁 → 偏向锁 → 轻量级锁 → 重量级锁
       ↓
    只有一个线程
                   ↓
              多个线程竞争
                              ↓
                         竞争激烈
锁状态原理适用场景
偏向锁对象头记录线程 ID只有一个线程
轻量级锁CAS + 自旋短时间竞争
重量级锁OSMutex长时间竞争

对象头结构(64位 JVM)

┌─────────────────────────────────────────────────────┐
│                     Mark Word (64 bits)              │
├─────────────────────────────────────────────────────┤
│ 无锁:25位 hashCode | 4位分代年龄 | 1位偏向锁 | 2位锁类型 │
│ 偏向锁:54位线程ID | 2位 Epoch | 4位分代年龄 | 1位偏向 | 2位 │
│ 轻量级锁:62位指向栈中锁记录的指针                   │
│ 重量级锁:62位指向互斥量(重量级锁)的指针           │
└─────────────────────────────────────────────────────┘

synchronized 的特点

  • 自动释放:代码块执行完或异常,自动释放锁
  • 可重入:同一个线程可以多次获取同一把锁
  • 非公平:不保证等待顺序

ReentrantLock 详解

基本用法

// 创建可重入锁(默认非公平)
private final ReentrantLock lock = new ReentrantLock();

// 获取锁(阻塞)
public void method() {
    lock.lock();
    try {
        // 临界区
    } finally {
        lock.unlock();  // 必须手动释放
    }
}

// 尝试获取锁(非阻塞)
public void tryMethod() {
    if (lock.tryLock()) {
        try {
            // 临界区
        } finally {
            lock.unlock();
        }
    } else {
        // 获取失败
    }
}

// 带超时获取
public void timeoutMethod() {
    try {
        if (lock.tryLock(5, TimeUnit.SECONDS)) {
            try {
                // 临界区
            } finally {
                lock.unlock();
            }
        }
    } catch (InterruptedException e) {
        // 等待被中断
    }
}

公平锁 vs 非公平锁

// 公平锁:按等待顺序获取
ReentrantLock fairLock = new ReentrantLock(true);

// 非公平锁:可能有插队(默认)
ReentrantLock unfairLock = new ReentrantLock(false);

// synchronized 是非公平锁

公平锁开销大,因为要维护等待队列。非公平锁效率高,但可能产生"饥饿"。

Condition 条件变量

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

// 等待
public void await() throws InterruptedException {
    lock.lock();
    try {
        while (/*条件不满足*/) {
            condition.await();  // 等待,释放锁
        }
        // 条件满足,继续执行
    } finally {
        lock.unlock();
    }
}

// 唤醒
public void signal() {
    lock.lock();
    try {
        // 条件满足,唤醒一个等待线程
        condition.signal();
    } finally {
        lock.unlock();
    }
}

对比

  • synchronized + Object.wait():只能有一个等待集
  • ReentrantLock + Condition:可以创建多个条件

对比详解

1. 等待唤醒机制

// synchronized
synchronized (obj) {
    while (条件不满足) {
        obj.wait();
    }
    // 处理
}
obj.notify();  // 或 notifyAll

// ReentrantLock + Condition
lock.lock();
try {
    while (条件不满足) {
        condition.await();
    }
    // 处理
} finally {
    lock.unlock();
}
condition.signal();  // 或 signalAll

2. 可中断等待

// ReentrantLock 支持
public void interruptible() throws InterruptedException {
    lock.lockInterruptibly();  // 可中断的获取
    try {
        // 临界区
    } finally {
        lock.unlock();
    }
}

synchronized 不支持中断,只能一直等待。

3. 多条件支持

// ReentrantLock 可以创建多个 Condition
private final ReentrantLock lock = new ReentrantLock();
private final Condition conditionA = lock.newCondition();
private final Condition conditionB = lock.newCondition();

// 等待 buffer 满
conditionA.await();

// 等待 buffer 空
conditionB.await();

// 生产者唤醒消费者,消费者唤醒生产者
conditionA.signal();  // buffer 满,唤醒消费
conditionB.signal();  // buffer 空,唤醒生产

synchronized 只有一个隐式的等待集,多条件场景不灵活。


生产者-消费者示例

synchronized 版本

public class Buffer {
    private int[] items = new int[100];
    private int count = 0;

    public synchronized void put(int item) throws InterruptedException {
        while (count == items.length) {
            wait();  // buffer 满
        }
        items[count++] = item;
        notifyAll();  // 唤醒消费者
    }

    public synchronized int take() throws InterruptedException {
        while (count == 0) {
            wait();  // buffer 空
        }
        int item = items[--count];
        notifyAll();  // 唤醒生产者
        return item;
    }
}

ReentrantLock 版本

public class Buffer {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    private int[] items = new int[100];
    private int count = 0;

    public void put(int item) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) {
                notFull.await();  // buffer 满
            }
            items[count++] = item;
            notEmpty.signal();  // 唤醒消费者
        } finally {
            lock.unlock();
        }
    }

    public int take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                notEmpty.await();  // buffer 空
            }
            int item = items[--count];
            notFull.signal();  // 唤醒生产者
            return item;
        } finally {
            lock.unlock();
        }
    }
}

面试高频问题

Q1:synchronized 是可重入锁吗?

。同一个线程可以多次获取同一把锁。

public synchronized void methodA() {
    methodB();  // 可以获取,因为是同一个线程
}

public synchronized void methodB() {
    // 同一把锁,可以进入
}

Q2:synchronized 和 ReentrantLock 性能哪个好?

  • JDK 1.6 之前:ReentrantLock 性能更好
  • JDK 1.6 之后:synchronized 引入锁升级,性能已接近
  • 现在:普通场景用 synchronized(更简洁),复杂场景用 ReentrantLock(更灵活)

Q3:Lock 和 synchronized 选择?

// 简单同步 → synchronized
synchronized (obj) {
    // 临界区
}

// 需要这些特性 → ReentrantLock
// - 公平锁
// - 可中断
// - 多条件
// - 超时获取

Q4:synchronized 加在静态方法和实例方法上的区别?

class User {
    // 锁的是 Class 对象(User.class)
    public static synchronized void staticMethod() { }
    
    // 锁的是 this(具体对象)
    public synchronized void method() { }
}

总结

选 synchronized:简单场景,代码简洁,自动释放

选 ReentrantLock:需要公平锁、多条件、可中断、超时等高级特性

synchronized:
├─ JVM 内置,字节码 monitorenter/monitorexit
├─ 自动释放
├─ 锁升级(偏向→轻量→重量)
└─ 无法中断、无多条件

ReentrantLock:
├─ JDK API,可重入
├─ 手动释放(必须 finally)
├─ 公平/非公平
├─ Condition 多条件
└─ tryLock 超时、可中断

评论区