Java中ReentrantLock与synchronized的区别及适用场景
Synchronized vs ReentrantLock: 差异、适用场景与性能分析
核心差异
两者都是Java中实现线程同步的核心手段,但在功能特性和使用方式上存在明显区别:
- 语法与锁释放机制:
synchronized是Java关键字,无需手动管理锁释放——线程退出同步代码块/方法时,JVM会自动释放锁,即使发生异常也不会遗漏。ReentrantLock是java.util.concurrent.locks包下的类,必须手动调用lock()获取锁,且必须在finally块中执行unlock()释放锁,否则会导致锁泄漏。
- 锁获取灵活性:
ReentrantLock支持多种灵活的锁获取方式:tryLock():非阻塞式获取锁,成功返回true,失败直接返回false,不会阻塞线程。tryLock(long timeout, TimeUnit unit):指定超时时间获取锁,超时未获取则返回false。lockInterruptibly():允许在等待锁的过程中响应线程中断,避免线程无限期阻塞。
synchronized不支持上述特性,线程进入锁等待状态后无法被中断,只能等待锁释放或JVM终止。
- 公平性支持:
ReentrantLock构造时可指定公平模式(new ReentrantLock(true)),公平锁会按线程请求锁的顺序分配锁,避免饥饿问题。synchronized仅支持非公平锁,JVM会随机唤醒等待队列中的线程,可能导致部分线程长期无法获取锁。
- 条件队列支持:
ReentrantLock可通过newCondition()创建多个独立的Condition对象,每个Condition对应专属的等待/通知队列,能实现更精细的线程协作(比如区分“队列空”和“队列满”的不同等待场景)。synchronized只能依赖对象本身的单一等待队列,通过wait()/notify()/notifyAll()实现通知,notifyAll()会唤醒所有等待线程,效率较低。
适用场景选择
- 优先使用
ynchronized的场景:- 简单的同步需求,比如单个方法或小型代码块的同步。
- 不需要额外锁功能的场景,
synchronized语法简洁,无需手动管理锁释放,出错概率更低。 - 大多数日常业务场景,JVM对
ynchronized的优化已经足够成熟,性能表现优异。
- 优先使用
ReentrantLock的场景:- 需要中断等待锁的线程:比如线程在等待锁时需要响应中断信号,避免无限期阻塞。
- 需要尝试非阻塞获取锁:比如希望在获取锁失败时执行其他逻辑,而非阻塞等待。
- 需要公平锁:比如必须保证线程按请求顺序获取锁,避免饥饿问题。
- 需要多条件队列的线程协作:比如生产者消费者模型中,需要分别处理队列空和队列满的等待逻辑。
性能影响
- JDK版本差异:
- JDK 1.6之前,
synchronized是重量级锁,性能远低于ReentrantLock。 - JDK 1.6之后,JVM引入了偏向锁、轻量级锁、自旋锁等一系列优化,
synchronized的性能大幅提升,与ReentrantLock在大部分场景下差异极小。
- JDK 1.6之前,
- 极端场景表现:
- 在高并发且锁竞争非常激烈的场景下,
ReentrantLock可能略占优势,但这种场景在日常开发中并不常见。 - 需注意:
ReentrantLock如果忘记在finally中释放锁,会导致锁泄漏,引发性能下降甚至死锁,这是比性能差异更严重的问题。
- 在高并发且锁竞争非常激烈的场景下,
代码示例
1. Synchronized实现计数器
public class SyncCounter { private int count = 0; public void increment() { // 同步代码块,锁对象为当前实例 synchronized (this) { count++; } } public int getCount() { synchronized (this) { return count; } } }
2. ReentrantLock实现可尝试获取锁的计数器
import java.util.concurrent.locks.ReentrantLock; public class LockCounter { private int count = 0; private final ReentrantLock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { // 必须在finally中释放锁,避免异常导致锁泄漏 lock.unlock(); } } // 尝试非阻塞获取锁,获取失败直接返回 public boolean tryIncrement() { if (lock.tryLock()) { try { count++; return true; } finally { lock.unlock(); } } return false; } public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }
3. ReentrantLock多条件队列实现有界队列
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class BoundedQueue<T> { private final T[] items; private int head, tail, count; private final ReentrantLock lock = new ReentrantLock(); // 两个独立的条件队列:队列非空、队列非满 private final Condition notEmpty = lock.newCondition(); private final Condition notFull = lock.newCondition(); @SuppressWarnings("unchecked") public BoundedQueue(int capacity) { items = (T[]) new Object[capacity]; } public void put(T item) throws InterruptedException { // 支持中断的锁获取 lock.lockInterruptibly(); try { // 队列满时,等待非满条件 while (count == items.length) { notFull.await(); } items[tail] = item; tail = (tail + 1) % items.length; count++; // 通知等待非空条件的线程 notEmpty.signal(); } finally { lock.unlock(); } } public T take() throws InterruptedException { lock.lockInterruptibly(); try { // 队列空时,等待非空条件 while (count == 0) { notEmpty.await(); } T item = items[head]; head = (head + 1) % items.length; count--; // 通知等待非满条件的线程 notFull.signal(); return item; } finally { lock.unlock(); } } }
内容的提问来源于stack exchange,提问作者Temiloluwa
相关产品推荐
相关产品推荐

