Lock接口及其实现类:Java 并发编程的灵活锁机制
Lock
接口是 JDK 5 引入的同步工具,为 Java 并发编程提供了比 synchronized
更灵活的锁控制能力。它支持显式加锁 / 解锁、可中断获取、超时获取等特性,弥补了 synchronized
隐式操作的局限性。本文将深入解析 Lock
接口的核心实现类(ReentrantLock
和 ReentrantReadWriteLock
),并对比其与 synchronized
的差异。
Lock 接口核心方法
Lock
接口定义了锁的基本操作,核心方法如下:
方法 | 作用描述 |
---|---|
lock() |
获取锁(阻塞式,直到获取成功) |
lockInterruptibly() |
可中断地获取锁(获取过程中若线程被中断,会抛出 InterruptedException ) |
tryLock() |
尝试非阻塞获取锁(成功返回 true ,失败返回 false ) |
tryLock(long timeout, TimeUnit unit) |
超时获取锁(超时未获取则返回 false ) |
unlock() |
释放锁(必须在 finally 块中调用,避免锁泄漏) |
newCondition() |
创建条件对象,实现线程间通信(类似 synchronized 的 wait/notify ) |
ReentrantLock:可重入独占锁
ReentrantLock
是 Lock
接口的最常用实现,提供与 synchronized
类似的独占锁功能,但支持更灵活的控制。其核心特性是可重入性(同一线程可多次获取锁)和公平 / 非公平模式。
公平锁与非公平锁
ReentrantLock
允许通过构造函数指定锁的公平性:
1 | // 非公平锁(默认) |
(1)非公平锁(NonfairSync)
- 获取逻辑:线程尝试直接获取锁(CAS 操作),失败后才进入等待队列;
- 特点:可能存在线程饥饿(先请求的线程后获取锁),但吞吐量更高;
- 适用场景:并发激烈但允许偶尔不公平的场景。
(2)公平锁(FairSync)
- 获取逻辑:线程必须按请求顺序进入队列,只有队首线程能获取锁;
- 特点:保证公平性,避免饥饿,但频繁的上下文切换导致吞吐量较低;
- 适用场景:对公平性要求高的场景(如资源调度)。
核心源码解析
ReentrantLock
基于 AQS(AbstractQueuedSynchronizer)实现,内部通过 Sync
抽象类区分公平与非公平逻辑:
获取锁(lock()
方法)
1 | // 非公平锁的 lock() 实现 |
释放锁(unlock()
方法)
1 | public void unlock() { |
多条件等待(Condition)
ReentrantLock
可通过 newCondition()
创建多个条件对象,实现更精细的线程通信:
1 | Lock lock = new ReentrantLock(); |
优势:相比 synchronized
的单条件队列,Condition
支持多条件分离,减少不必要的唤醒。
ReentrantReadWriteLock:读写分离锁
ReentrantReadWriteLock
是为读多写少场景优化的锁,其核心是读写分离:
- 读锁(ReadLock):共享锁,允许多个线程同时读取;
- 写锁(WriteLock):独占锁,同一时间仅允许一个线程写入。
核心特性
- 互斥规则:读锁与写锁互斥,写锁与写锁互斥,读锁与读锁不互斥;
- 可重入性:读锁和写锁均支持重入(读锁重入需当前线程未持有写锁);
- 降级与升级:写锁可降级为读锁(先获取写锁,再获取读锁,最后释放写锁),但读锁不可升级为写锁(避免死锁)。
状态设计
ReentrantReadWriteLock
复用 AQS 的 state
变量,通过位分割存储读写状态:
- 低 16 位:写锁的重入计数(
exclusiveCount
); - 高 16 位:读锁的持有计数(
sharedCount
)。
1 | static final int SHARED_SHIFT = 16; |
读锁与写锁的获取与释放
读锁(ReadLock)
- 获取:若无线程持有写锁,或持有写锁的是当前线程,则累加读锁计数;
- 释放:递减读锁计数,直至为 0 时完全释放。
1 | public void lock() { |
写锁(WriteLock)
- 获取:若无线程持有读锁或写锁,或当前线程已持有写锁,则累加写锁计数;
- 释放:递减写锁计数,直至为 0 时完全释放。
Lock 与 synchronized 的对比
特性 | Lock(ReentrantLock) | synchronized |
---|---|---|
锁获取方式 | 显式(lock() /unlock() ) |
隐式(代码块 / 方法修饰) |
公平性 | 支持公平 / 非公平模式 | 仅非公平模式 |
可中断性 | 支持(lockInterruptibly() ) |
不支持(中断后仍会获取锁) |
超时获取 | 支持(tryLock(timeout) ) |
不支持 |
条件等待 | 多条件(Condition ) |
单条件(对象监视器) |
性能 | 高并发下更优(减少上下文切换) | JVM 优化后与 Lock 接近 |
适用场景 | 复杂同步逻辑(超时、中断、多条件) | 简单同步场景 |
锁的分类与最佳实践
锁的分类
- 按可重入性:可重入锁(
synchronized
、ReentrantLock
)与不可重入锁; - 按公平性:公平锁与非公平锁;
- 按共享模式:独占锁(
ReentrantLock
、写锁)与共享锁(读锁、Semaphore
)。
最佳实践
- 优先使用
synchronized
:简单场景下代码更简洁,JVM 优化更成熟; - 使用
ReentrantLock
:需中断、超时或多条件等待时; - 使用
ReentrantReadWriteLock
:读多写少场景(如缓存、配置中心); - 避免死锁:始终在
finally
块中释放锁,按顺序获取多把锁。