Java中的锁机制:ReentrantLock与synchronized对比
字数 1866 2025-11-03 08:33:37
Java中的锁机制:ReentrantLock与synchronized对比
描述
在多线程编程中,锁是保证线程安全的核心工具。Java提供了两种主要的锁机制:synchronized关键字(内置锁)和ReentrantLock(显式锁)。虽然二者都能实现同步,但在设计理念、功能灵活性和底层实现上存在显著差异。理解它们的区别有助于在实际场景中做出合理选择。
知识要点分步讲解
1. 基础概念:锁的作用与可重入性
- 锁的核心作用:确保同一时间只有一个线程能访问共享资源,避免数据竞争。
- 可重入性:指线程可以重复获取自己已经持有的锁。例如,在递归方法或同步方法调用另一个同步方法时,若锁不可重入,会导致线程自我阻塞。
synchronized和ReentrantLock都是可重入锁。// synchronized的可重入示例 public synchronized void methodA() { methodB(); // 线程可直接进入methodB的同步块,不会阻塞 } public synchronized void methodB() { }
2. synchronized关键字详解
- 用法:
- 修饰实例方法:锁对象是当前实例(
this)。 - 修饰静态方法:锁对象是当前类的Class对象(如
MyClass.class)。 - 同步代码块:需显式指定锁对象(如
synchronized(obj))。
- 修饰实例方法:锁对象是当前实例(
- 特点:
- JVM内置实现:无需手动加锁/解锁,由JVM通过监视器(Monitor)管理。
- 自动释放锁:线程执行完同步代码或发生异常时,JVM自动释放锁。
- 非中断等待:线程在等待锁时无法被中断,可能造成死锁难以解除。
3. ReentrantLock显式锁详解
- 用法:需显式创建锁对象,手动调用
lock()和unlock()。ReentrantLock lock = new ReentrantLock(); lock.lock(); try { // 临界区代码 } finally { lock.unlock(); // 必须放在finally中确保锁释放 } - 核心优势:
- 可中断等待:通过
lockInterruptibly()方法,等待锁的线程可响应中断。 - 公平锁选项:构造函数传入
true可创建公平锁(按等待时间分配锁),默认非公平锁(吞吐量更高)。 - 条件变量(Condition):支持多个等待队列,例如在生产者-消费者模型中,可分别管理“队列满”和“队列空”的等待线程。
Condition notEmpty = lock.newCondition(); Condition notFull = lock.newCondition();
- 可中断等待:通过
4. 功能对比与选择策略
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 锁获取方式 | 隐式,JVM管理 | 显式,代码控制 |
| 中断响应 | 不支持 | 支持lockInterruptibly() |
| 公平锁 | 非公平(JVM优化) | 可配置公平性 |
| 条件变量 | 仅通过wait()/notify() |
支持多个Condition |
| 锁释放 | 自动释放 | 必须手动解锁,否则可能死锁 |
选择建议:
- 优先使用
synchronized:代码简洁,JVM持续优化其性能(如锁升级机制)。 - 需高级功能时选
ReentrantLock:如公平锁、可中断、精确的条件控制。
5. 底层实现原理简析
- synchronized:
- 通过对象头的Mark Word记录锁状态(无锁、偏向锁、轻量级锁、重量级锁)。
- 锁升级过程:偏向锁(单线程)→ 轻量级锁(少量竞争)→ 重量级锁(激烈竞争)。
- ReentrantLock:
- 基于AQS(AbstractQueuedSynchronizer)队列同步器,通过CAS操作维护线程排队。
- 非公平锁直接尝试抢占,公平锁先检查队列是否有等待线程。
总结
synchronized是Java原生的轻量级同步工具,适用于大多数简单场景;ReentrantLock提供了更灵活的API,适合复杂的并发控制。理解二者的底层机制和适用场景,是编写高效、健壮多线程代码的基础。