一、基本特性对比
特性 |
synchronized |
ReentrantLock |
锁的实现机制 |
JVM 内置关键字,通过监视器实现 |
JDK 提供的 API 类( |
锁的获取方式 |
隐式获取和释放(进入/退出同步代码块或方法自动获取/释放) |
显式调用 |
可重入性 |
支持 |
支持 |
锁的类型 |
非公平锁(默认) |
可选择公平锁或非公平锁(构造函数指定) |
条件变量 |
通过 |
通过 |
中断响应 |
不支持中断等待 |
支持 |
超时机制 |
不支持 |
支持 |
锁的绑定 |
与代码块或方法绑定 |
可跨方法绑定,更灵活 |
性能 |
JDK 1.6 后优化,性能接近 |
在高并发竞争下表现更稳定 |
二、详细区别分析
1. 实现层面
- synchronized:
- Java 关键字,由 JVM 底层实现(通过
monitorenter/monitorexit字节码指令)。 - 锁信息记录在对象头的 Mark Word 中。
- ReentrantLock:
- 基于
AbstractQueuedSynchronizer(AQS)实现的显式锁。 - 通过 CAS(Compare-And-Swap)和队列管理线程竞争。
2. 使用方式
// synchronized 隐式使用 public synchronized void method() { // 同步代码 } // 或 public void method() { synchronized(this) { // 同步代码 } } // ReentrantLock 显式使用 private ReentrantLock lock = new ReentrantLock(); public void method() { lock.lock(); try { // 同步代码 } finally { lock.unlock(); // 必须手动释放 } }
3. 公平性选择
- synchronized:仅支持非公平锁(线程竞争时随机获取锁)。
- ReentrantLock:
- 公平锁:按等待时间顺序获取锁,避免线程饥饿,但性能较低。
- 非公平锁:允许插队,性能更高。
4. 条件变量(Condition)
- synchronized:通过
Object.wait()/notify()实现等待/唤醒,只能有一个等待队列。 - ReentrantLock:可创建多个
Condition对象,实现精细化的线程等待/唤醒。
Condition condition = lock.newCondition(); condition.await(); // 类似 wait() condition.signal(); // 类似 notify()
示例:生产者-消费者模型中,可为空队列和满队列分别设置 Condition。
5. 中断与超时
- synchronized:
- 线程等待锁时无法被中断。
- 无超时机制,可能永久等待。
- ReentrantLock:
// 支持中断 lock.lockInterruptibly(); // 支持超时 if (lock.tryLock(1, TimeUnit.SECONDS)) { try { /* 操作 */ } finally { lock.unlock(); } }
6. 性能差异
- JDK 1.5 时
ReentrantLock性能显著优于synchronized。 - JDK 1.6 后 JVM 对
synchronized进行了大量优化(锁升级、自适应自旋等),两者性能差距缩小。 - 在高竞争场景下,
ReentrantLock仍可能表现更稳定。
三、适用场景
优先使用 synchronized 的情况
- 简单的同步场景,代码简洁性更重要。
- 不需要高级功能(如条件变量、中断、超时)。
- 资源竞争不激烈时,性能可接受。
优先使用 ReentrantLock 的情况
- 需要公平锁、可中断锁、超时锁等高级功能。
- 需要多个条件变量(如阻塞队列的实现)。
- 需要跨方法加锁/释放锁(如:在方法 A 加锁,在方法 B 释放)。
- 竞争激烈且性能要求高。
四、示例对比
场景:生产者-消费者模型
// 使用 synchronized(单一条件) public synchronized void put(Object item) throws InterruptedException { while (queue.isFull()) { wait(); // 只能在一个条件上等待 } queue.put(item); notifyAll(); } // 使用 ReentrantLock(多条件) private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); public void put(Object item) throws InterruptedException { lock.lock(); try { while (queue.isFull()) { notFull.await(); // 只在 "非满" 条件上等待 } queue.put(item); notEmpty.signal(); // 只唤醒等待 "非空" 的线程 } finally { lock.unlock(); } }
五、总结
- synchronized 简单、安全、自动管理锁释放,适合大多数常规同步场景。
- ReentrantLock 功能强大、灵活可控,适合复杂并发场景和高级需求。
- 从 JDK 1.6 开始,两者性能接近,选择时应更关注功能需求和代码可维护性。
- 在 JDK 后续版本中,
synchronized仍在持续优化(如锁消除、锁粗化等),而ReentrantLock提供了更细粒度的并发控制。
面试回答
首先,synchronized 是 Java 语言层面的关键字,是 JVM 原生支持的锁机制。它的使用非常简单,编译器会自动处理锁的获取和释放,所以基本不会因为忘记释放锁而导致死锁,易用性是它的最大优点。
而 ReentrantLock 是 JUC 包下的一个类,是 JDK 层面实现的锁。它需要开发者显式地调用 lock() 和 unlock() 方法,通常在 finally 块中释放锁,否则容易出问题。所以从使用门槛上说,synchronized 更低。
在功能上,ReentrantLock 比 synchronized 灵活和强大得多,主要有三点:
- 可中断获取锁:当线程尝试获取
ReentrantLock时,如果长时间拿不到,可以响应中断,通过lockInterruptibly()方法放弃等待去做别的事情。而synchronized在等待锁时,线程会一直阻塞,无法被中断。 - 公平锁选项:
ReentrantLock可以在构造函数中指定是否是公平锁(先等待的线程先获得锁)。虽然公平锁性能有损耗,但能防止线程饥饿。synchronized则是非公平的,谁抢到算谁的,性能通常更好。 - 条件变量(Condition):这是非常强大的一点。一个
ReentrantLock可以创建多个Condition对象,用来实现更精细的线程等待/通知。比如,我们可以让一部分线程在条件A上等待,另一部分在条件B上等待,唤醒时也可以选择只唤醒等待条件A的线程。而synchronized只能配合wait()和notify(),所有线程都在同一个条件队列上,唤醒是随机的(notify)或全部唤醒(notifyAll),不够精确。
在早期版本(JDK 1.5 之前),ReentrantLock 的性能比 synchronized 好很多。但后来 JVM 对 synchronized 进行了大幅优化,比如引入了偏向锁、轻量级锁、自旋锁、锁消除、锁粗化等。所以在高版本的 JDK(如 1.8 及以后)中,两者在性能上已经相差无几,synchronized 甚至在一些常见场景下更优,因为它有 JVM 的持续优化。
所以,我的选择原则通常是:
- 优先考虑
synchronized:在满足需求的情况下,因为它简单、安全(自动释放),且性能不差。大部分标准的同步场景用它就够了。 - 需要高级功能时再用
ReentrantLock:比如我需要用到可中断、公平锁,或者需要复杂的条件等待机制(典型应用就是“生产者-消费者”模型),这时ReentrantLock是唯一的选择。