Java 中 synchronized 与 ReentrantLock 的区别
synchronized 是 JVM 内置关键字,自动管理锁释放,适合简单场景。ReentrantLock 基于 AQS 实现,支持公平锁、中断响应、超时及多条件变量,功能更灵活。JDK 1.6 后两者性能接近,高并发下 ReentrantLock 表现更稳定。选择时应优先考虑功能需求,常规同步用 synchronized,复杂控制用 ReentrantLock。

synchronized 是 JVM 内置关键字,自动管理锁释放,适合简单场景。ReentrantLock 基于 AQS 实现,支持公平锁、中断响应、超时及多条件变量,功能更灵活。JDK 1.6 后两者性能接近,高并发下 ReentrantLock 表现更稳定。选择时应优先考虑功能需求,常规同步用 synchronized,复杂控制用 ReentrantLock。

特性 | synchronized | ReentrantLock |
锁的实现机制 | JVM 内置关键字,通过监视器实现 | JDK 提供的 API 类( |
锁的获取方式 | 隐式获取和释放(进入/退出同步代码块或方法自动获取/释放) | 显式调用 |
可重入性 | 支持 | 支持 |
锁的类型 | 非公平锁(默认) | 可选择公平锁或非公平锁(构造函数指定) |
条件变量 | 通过 | 通过 |
中断响应 | 不支持中断等待 | 支持 |
超时机制 | 不支持 | 支持 |
锁的绑定 | 与代码块或方法绑定 | 可跨方法绑定,更灵活 |
性能 | JDK 1.6 后优化,性能接近 | 在高并发竞争下表现更稳定 |
monitorenter/monitorexit 字节码指令)。AbstractQueuedSynchronizer(AQS) 实现的显式锁。// 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(); // 必须手动释放
}
}
Object.wait()/notify() 实现等待/唤醒,只能有一个等待队列。Condition 对象,实现精细化的线程等待/唤醒。Condition condition = lock.newCondition();
condition.await(); // 类似 wait()
condition.signal(); // 类似 notify()
示例:生产者 - 消费者模型中,可为空队列和满队列分别设置 Condition。
// 支持中断
lock.lockInterruptibly();
// 支持超时
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
/* 操作 */
} finally {
lock.unlock();
}
}
ReentrantLock 性能显著优于 synchronized。synchronized 进行了大量优化(锁升级、自适应自旋等),两者性能差距缩小。ReentrantLock 仍可能表现更稳定。// 使用 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 提供了更细粒度的并发控制。首先,synchronized 是 Java 语言层面的关键字,是 JVM 原生支持的锁机制。它的使用非常简单,编译器会自动处理锁的获取和释放,所以基本不会因为忘记释放锁而导致死锁,易用性是它的最大优点。
而 ReentrantLock 是 JUC 包下的一个类,是 JDK 层面实现的锁。它需要开发者显式地调用 lock() 和 unlock() 方法,通常在 finally 块中释放锁,否则容易出问题。所以从使用门槛上说,synchronized 更低。
在功能上,ReentrantLock 比 synchronized 灵活和强大得多,主要有三点:
ReentrantLock 时,如果长时间拿不到,可以响应中断,通过 lockInterruptibly() 方法放弃等待去做别的事情。而 synchronized 在等待锁时,线程会一直阻塞,无法被中断。ReentrantLock 可以在构造函数中指定是否是公平锁(先等待的线程先获得锁)。虽然公平锁性能有损耗,但能防止线程饥饿。synchronized 则是非公平的,谁抢到算谁的,性能通常更好。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 是唯一的选择。
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online