synchronized vs ReentrantLock

面试中的灵魂拷问

面试官问:"synchronized和ReentrantLock有什么区别?"

候选人小张想了想,说:"synchronized是关键字,ReentrantLock是类。"

面试官追问:"还有呢?"

小张:"synchronized自动释放锁,ReentrantLock要手动释放..."

面试官点点头,继续问:"那你什么时候用synchronized,什么时候用ReentrantLock?"

小张停顿了一下:"呃...大部分时候用synchronized?"

这个问题看似基础,但能回答好的人不多。大部分人只能说出"自动释放 vs 手动释放",但对为什么需要ReentrantLock各自的适用场景性能差异理解不深。

今天这篇文章,把两者的区别彻底讲清楚。

基本概念对比

synchronized

// 修饰实例方法
public synchronized void method() {
    // 锁对象是 this
}

// 修饰静态方法
public static synchronized void staticMethod() {
    // 锁对象是 Class 对象
}

// 修饰代码块
public void blockMethod() {
    synchronized (this) {
        // 锁对象是指定对象
    }
}

特点

  • Java语言内置的关键字
  • JVM层面的实现(monitorenter/monitorexit)
  • 自动获取和释放锁
  • 锁不能中断、不能超时、不能非阻塞获取

ReentrantLock

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {
    private final ReentrantLock lock = new ReentrantLock();
    
    public void method() {
        lock.lock();  // 获取锁
        try {
            // 业务逻辑
        } finally {
            lock.unlock();  // 必须在finally中释放
        }
    }
    
    // 非公平锁(默认)
    private final ReentrantLock unfairLock = new ReentrantLock();
    
    // 公平锁
    private final ReentrantLock fairLock = new ReentrantLock(true);
    
    // 可中断获取
    public void interruptibleMethod() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            // 业务逻辑
        } finally {
            lock.unlock();
        }
    }
    
    // 尝试获取(不阻塞)
    public void tryLockMethod() {
        if (lock.tryLock()) {  // 立即返回
            try {
                // 获取成功
            } finally {
                lock.unlock();
            }
        } else {
            // 获取失败,做其他事
        }
    }
    
    // 带超时获取
    public void tryLockWithTimeout() throws InterruptedException {
        if (lock.tryLock(5, TimeUnit.SECONDS)) {
            try {
                // 5秒内获取到锁
            } finally {
                lock.unlock();
            }
        } else {
            // 超时,获取失败
        }
    }
}

核心区别对比

功能特性对比

特性synchronizedReentrantLock
实现层次JVM内置JDK API
锁获取自动获取手动调用lock()
锁释放自动释放必须finally中unlock()
中断等待不支持lockInterruptibly()
超时获取不支持tryLock(time)
非阻塞获取不支持tryLock()
公平锁非公平可公平/非公平
多条件等待Object.wait/notify多个Condition
可重入支持支持
性能JDK 6后优化良好略优于synchronized

可见性保证

两者都保证可见性有序性

// synchronized
synchronized (lock) {
    x = 10;  // happens-before unlock
}

// ReentrantLock
lock.lock();
try {
    x = 10;  // happens-before unlock
} finally {
    lock.unlock();
}

happens-before链

  • lock() happens-before unlock()
  • unlock() happens-before 后续lock()
  • 临界区内的操作 happens-before unlock()

ReentrantLock的高级特性

公平锁 vs 非公平锁

非公平锁(默认)

ReentrantLock lock = new ReentrantLock();  // 非公平

public void lockMethod() {
    lock.lock();
    try {
        // 业务逻辑
    } finally {
        lock.unlock();
    }
}

获取流程

  1. 尝试CAS直接获取锁(插队)
  2. 如果成功,立即执行
  3. 如果失败,排队等待

公平锁

ReentrantLock lock = new ReentrantLock(true);  // 公平

public void lockMethod() {
    lock.lock();
    try {
        // 业务逻辑
    } finally {
        lock.unlock();
    }
}

获取流程

  1. 检查等待队列是否有更早的线程
  2. 如果有,乖乖去排队
  3. 如果没有,尝试获取锁
// 公平锁的实现伪代码
protected final boolean tryAcquire(int acquires) {
    // 检查是否有前驱节点
    if (hasQueuedPredecessor()) {
        return false;  // 有更早等待的线程,不获取
    }
    // 没有更早等待的线程,尝试CAS获取
    return compareAndSetState(0, acquires);
}

// 非公平锁的实现伪代码
final void lock() {
    // 直接尝试CAS获取,不检查队列
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

公平 vs 非公平的权衡

维度非公平锁公平锁
吞吐量高(可插队)低(必须排队)
响应性更快(立即执行)可能延迟
饥饿风险存在不存在
适用场景高并发场景锁竞争不激烈的场景
// 非公平锁可能导致饥饿的例子
public class StarvationDemo {
    private final ReentrantLock lock = new ReentrantLock(false);
    
    public void highPriority() {
        // 高优先级线程不断插队
        while (true) {
            if (lock.tryLock()) {  // 插队
                try {
                    // 快速执行
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    
    public void lowPriority() {
        // 低优先级线程可能永远得不到锁
        lock.lock();
        try {
            // 可能永远执行不到
        } finally {
            lock.unlock();
        }
    }
}

可中断等待

synchronized不能中断

// ❌ synchronized无法响应中断
public class SyncInterruptDemo {
    private final Object lock = new Object();
    
    public void method() {
        synchronized (lock) {
            // 阻塞在这个同步块中
            // 无法被中断
        }
    }
}

ReentrantLock支持中断

public class InterruptibleLockDemo {
    private final ReentrantLock lock = new ReentrantLock();
    
    public void interruptibleMethod() throws InterruptedException {
        lock.lockInterruptibly();  // 可中断获取
        try {
            // 业务逻辑
        } finally {
            lock.unlock();
        }
    }
    
    public static void main(String[] args) {
        InterruptibleLockDemo demo = new InterruptibleLockDemo();
        Thread t = new Thread(() -> {
            try {
                demo.interruptibleMethod();
            } catch (InterruptedException e) {
                System.out.println("被中断了");
            }
        });
        t.start();
        
        // 主线程中断t
        t.interrupt();
    }
}

使用场景

  • 任务可以被取消的场景
  • 限时任务
  • 优雅关闭

超时获取

synchronized不能超时

// ❌ 只能无限等待
public synchronized void blockingMethod() {
    // 如果拿不到锁,永远阻塞
}

ReentrantLock支持超时

public class TryLockDemo {
    private final ReentrantLock lock = new ReentrantLock();
    
    public void tryLockMethod() {
        try {
            // 尝试获取锁,最多等待5秒
            boolean acquired = lock.tryLock(5, TimeUnit.SECONDS);
            if (acquired) {
                try {
                    // 获取成功
                } finally {
                    lock.unlock();
                }
            } else {
                // 超时,获取失败
                System.out.println("获取锁超时");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

实际应用

public class OrderService {
    private final ReentrantLock lock = new ReentrantLock();
    private final Map<String, Integer> orders = new HashMap<>();
    
    public boolean tryCreateOrder(String orderId, int amount) {
        try {
            // 最多等待1秒获取锁
            if (lock.tryLock(1, TimeUnit.SECONDS)) {
                try {
                    if (orders.containsKey(orderId)) {
                        return false;  // 订单已存在
                    }
                    orders.put(orderId, amount);
                    return true;
                } finally {
                    lock.unlock();
                }
            } else {
                // 超时,可能是死锁或系统繁忙
                return false;
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
}

多个Condition

Object.wait/notify只能有一个条件队列

public class SingleConditionDemo {
    private final Object lock = new Object();
    private boolean resourceAvailable = false;
    private boolean processingDone = false;
    
    // ❌ 只能用一个wait set
    public synchronized void await() throws InterruptedException {
        while (!resourceAvailable) {
            wait();  // 只能等待resourceAvailable
        }
    }
    
    public synchronized void notifyResource() {
        resourceAvailable = true;
        notify();  // 可能唤醒错误的线程
    }
}

ReentrantLock支持多个Condition

import java.util.concurrent.locks.Condition;

public class MultipleConditionDemo {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition resourceCondition = lock.newCondition();
    private final Condition processingCondition = lock.newCondition();
    
    private boolean resourceAvailable = false;
    private boolean processingDone = false;
    
    public void awaitResource() throws InterruptedException {
        lock.lock();
        try {
            while (!resourceAvailable) {
                resourceCondition.await();  // 只等待资源
            }
        } finally {
            lock.unlock();
        }
    }
    
    public void awaitProcessing() throws InterruptedException {
        lock.lock();
        try {
            while (!processingDone) {
                processingCondition.await();  // 只等待处理完成
            }
        } finally {
            lock.unlock();
        }
    }
    
    public void signalResource() {
        lock.lock();
        try {
            resourceAvailable = true;
            resourceCondition.signal();  // 只唤醒等待资源的线程
        } finally {
            lock.unlock();
        }
    }
}

生产者-消费者模式

public class BoundedBuffer<T> {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    
    private final Object[] items;
    private int count = 0;
    private int putPtr = 0;
    private int takePtr = 0;
    
    public BoundedBuffer(int capacity) {
        items = new Object[capacity];
    }
    
    public void put(T item) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) {
                notFull.await();  // 缓冲区满,等待
            }
            items[putPtr] = item;
            if (++putPtr == items.length) putPtr = 0;
            count++;
            notEmpty.signal();  // 唤醒消费者
        } finally {
            lock.unlock();
        }
    }
    
    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                notEmpty.await();  // 缓冲区空,等待
            }
            @SuppressWarnings("unchecked")
            T item = (T) items[takePtr];
            if (++takePtr == items.length) takePtr = 0;
            count--;
            notFull.signal();  // 唤醒生产者
            return item;
        } finally {
            lock.unlock();
        }
    }
}

生产场景选型

默认选择synchronized

// ✅ 简单场景用synchronized
public class SimpleCounter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

适用synchronized的场景

  • 代码简单,逻辑不复杂
  • 不需要超时、中断、多条件
  • 锁竞争不激烈
  • 自动释放更安全

选择ReentrantLock的场景

// ✅ 需要公平锁
private final ReentrantLock fairLock = new ReentrantLock(true);

// ✅ 需要超时
public boolean tryLockWithTimeout() {
    try {
        return lock.tryLock(5, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        return false;
    }
}

// ✅ 需要可中断
public void interruptiblyLock() throws InterruptedException {
    lock.lockInterruptibly();
    try {
        // 业务逻辑
    } finally {
        lock.unlock();
    }
}

// ✅ 需要多个条件队列
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();

适用ReentrantLock的场景

  • 需要公平锁保证
  • 需要超时控制
  • 需要可中断等待
  • 需要多个等待条件
  • 需要tryLock()非阻塞获取

实际案例:限流器

public class RateLimiter {
    private final ReentrantLock lock = new ReentrantLock();
    private final long timeout;
    private final int maxPermits;
    private long permits;
    private long lastUpdateTime;
    
    public RateLimiter(int maxPermits, long timeout, TimeUnit unit) {
        this.maxPermits = maxPermits;
        this.timeout = unit.toMillis(timeout);
        this.permits = maxPermits;
        this.lastUpdateTime = System.currentTimeMillis();
    }
    
    public boolean tryAcquire() {
        lock.lock();
        try {
            long now = System.currentTimeMillis();
            // 补充令牌
            permits = Math.min(maxPermits, 
                permits + (now - lastUpdateTime) / timeout);
            lastUpdateTime = now;
            
            if (permits >= 1) {
                permits--;
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }
    
    public boolean acquire(long time, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(time);
        lock.lockInterruptibly();
        try {
            long now = System.currentTimeMillis();
            permits = Math.min(maxPermits,
                permits + (now - lastUpdateTime) / timeout);
            lastUpdateTime = now;
            
            long deadline = System.nanoTime() + nanos;
            while (permits < 1) {
                if (nanos <= 0) return false;
                nanos = notFull.awaitNanos(nanos);
            }
            permits--;
            return true;
        } finally {
            lock.unlock();
        }
    }
}

性能对比

JDK 6之后的优化

JDK 6对synchronized进行了大量优化:

  • 偏向锁
  • 轻量级锁
  • 锁消除
  • 锁粗化
  • 自适应自旋

现在synchronized和ReentrantLock性能差异很小。

benchmark测试

public class LockPerformanceTest {
    private final Object syncLock = new Object();
    private final ReentrantLock reentrantLock = new ReentrantLock();
    
    @Benchmark
    public void synchronizedBenchmark() {
        for (int i = 0; i < 1000; i++) {
            synchronized (syncLock) {
                // 空操作
            }
        }
    }
    
    @Benchmark
    public void reentrantLockBenchmark() {
        for (int i = 0; i < 1000; i++) {
            reentrantLock.lock();
            try {
                // 空操作
            } finally {
                reentrantLock.unlock();
            }
        }
    }
}

// 结果(JDK 17,无竞争情况):
// synchronized: ~100ns per lock
// ReentrantLock: ~150ns per lock
// 差异主要是手动lock/unlock的开销

常见误区

误区1:ReentrantLock一定比synchronized快

// ❌ 错误:在无竞争情况下
public void wrongOptimization() {
    ReentrantLock lock = new ReentrantLock();
    lock.lock();
    try {
        // 业务逻辑
    } finally {
        lock.unlock();
    }
    // 这里比synchronized多了try-finally的开销
}

// ✅ 正确:简单场景用synchronized
public void simpleSynchronized() {
    synchronized (this) {
        // 业务逻辑
    }
}

误区2:忘记在finally中释放锁

// ❌ 错误:异常时锁无法释放
public void wrongLock() {
    reentrantLock.lock();
    if (condition) {
        return;  // 锁泄漏!
    }
    reentrantLock.unlock();
}

// ✅ 正确:必须在finally中释放
public void correctLock() {
    reentrantLock.lock();
    try {
        if (condition) {
            return;
        }
    } finally {
        reentrantLock.unlock();
    }
}

误区3:混合使用synchronized和ReentrantLock

// ❌ 错误:混用导致死锁
private final Object syncLock = new Object();
private final ReentrantLock reentrantLock = new ReentrantLock();

public void deadlockMethod() {
    synchronized (syncLock) {
        reentrantLock.lock();  // 可能死锁
        try {
            // 业务逻辑
        } finally {
            reentrantLock.unlock();
        }
    }
}

// ✅ 正确:统一使用同一种锁
private final ReentrantLock lock = new ReentrantLock();

面试中的高频追问

追问1:ReentrantLock是如何实现可重入的?

通过AQS的state计数器实现:

  • 第一次获取锁:state从0变为1
  • 同一线程再次获取:state加1
  • 释放锁:state减1
  • state变为0时,完全释放锁

追问2:synchronized的锁能实现公平吗?

不能。synchronized只有非公平锁。JDK 6之前没有偏向锁/轻量级锁优化时,性能很差。

追问3:lockInterruptibly()和tryLock()有什么区别?

  • lockInterruptibly():阻塞等待,但可以被中断
  • tryLock():立即返回,不阻塞(获取成功/失败)
  • tryLock(timeout):等待指定时间,可以被中断

【学习小结】

  1. 默认用synchronized:简单场景、不需要高级特性时
  2. 用ReentrantLock:需要公平锁、超时、中断、多个条件
  3. synchronized优势:语法简洁、自动释放、不易出错
  4. ReentrantLock优势:功能丰富、可控制性强
  5. 性能差异不大:JDK 6后synchronized优化良好
  6. 必须配合try-finally:手动释放锁必须放在finally中
  7. 禁止混用:同一逻辑不能同时用两种锁

延伸阅读