面试被问"synchronized 和 ReentrantLock 的区别",背了几条区别但说不清"为什么要搞两个"。从头梳理一下 Java 锁的演进。
synchronized — 最简单的锁
public class Counter {
private int count = 0;
// 方法锁:锁的是 this 对象
public synchronized void increment() {
count++;
}
// 代码块锁:可以精确控制锁的范围
public void doSomething() {
// 这里不需要锁
synchronized (this) {
count++;
}
// 这里也不需要
}
// 静态方法锁:锁的是 Class 对象
public static synchronized void staticMethod() {
// ...
}
}
优点:简单,自动释放(不用手动 unlock)。 缺点:不够灵活——不能中断、不能超时、只能非公平。
ReentrantLock — 更灵活的锁
public class Counter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // 必须在 finally 里释放!
}
}
// 可中断的锁
public void incrementInterruptibly() throws InterruptedException {
lock.lockInterruptibly();
try {
count++;
} finally {
lock.unlock();
}
}
// 带超时的锁
public boolean tryIncrement() {
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
count++;
return true;
} finally {
lock.unlock();
}
}
return false; // 1 秒内没拿到锁
}
}
对比
| synchronized | ReentrantLock | |
|---|---|---|
| 使用方式 | 关键字,自动释放 | API,手动 lock/unlock |
| 可中断 | 不可中断 | lockInterruptibly() |
| 超时获取 | 不支持 | tryLock(timeout) |
| 公平锁 | 不支持 | new ReentrantLock(true) |
| 条件变量 | 只有一个(wait/notify) | 可以有多个 Condition |
| 性能 | JDK 6 后差不多 | 差不多 |
公平锁 vs 非公平锁
// 非公平锁(默认):新来的线程可能直接抢到锁
ReentrantLock unfairLock = new ReentrantLock();
// 公平锁:按排队顺序来,先到先得
ReentrantLock fairLock = new ReentrantLock(true);
公平锁的代价是吞吐量下降——每次都要检查队列里有没有人在等。大部分场景用非公平锁就够了。
Condition — 比 wait/notify 更精确
public class BoundedQueue<T> {
private final List<T> list = new ArrayList<>();
private final int capacity;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition(); // 生产者等这里
private final Condition notEmpty = lock.newCondition(); // 消费者等这里
public BoundedQueue(int capacity) {
this.capacity = capacity;
}
public void put(T item) throws InterruptedException {
lock.lock();
try {
while (list.size() == capacity) {
notFull.await(); // 满了,等"不满"的信号
}
list.add(item);
notEmpty.signal(); // 通知消费者:有数据了
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while (list.isEmpty()) {
notEmpty.await(); // 空的,等"不空"的信号
}
T item = list.remove(0);
notFull.signal(); // 通知生产者:有空位了
return item;
} finally {
lock.unlock();
}
}
}
synchronized 只有一个条件队列(wait/notifyAll),生产者和消费者互相干扰。ReentrantLock 可以有多个 Condition,各等各的。
ReadWriteLock — 读多写少场景
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();
// 读锁:多个线程可以同时持有
public String getData() {
readLock.lock();
try {
return data;
} finally {
readLock.unlock();
}
}
// 写锁:独占
public void updateData(String newData) {
writeLock.lock();
try {
data = newData;
} finally {
writeLock.unlock();
}
}
读读不互斥、读写互斥、写写互斥。适合缓存这种读多写少的场景。
实际选择
简单同步 → synchronized(够用就行)
需要超时/中断/公平 → ReentrantLock
读多写少 → ReadWriteLock
只是原子操作 → AtomicXxx(比锁更轻量)
// 原子操作,不需要锁
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 线程安全,比 synchronized 快得多
AtomicReference<User> userRef = new AtomicReference<>();
userRef.compareAndSet(oldUser, newUser); // CAS 操作
总结
Java 锁的演进:synchronized(简单够用)→ ReentrantLock(灵活:可中断、超时、公平)→ ReadWriteLock(读多写少)→ AtomicXxx(无锁原子操作)。新项目优先用 java.util.concurrent 包里的工具,synchronized 留给简单场景。