reentrantlock是java并发包(java.util.concurrent.locks)中提供的一种可重入互斥锁,它作为synchronized关键字的替代方案,提供了更灵活、更强大的线程同步机制。本文将全面解析reentrantlock的核心特性、实现原理及实际应用场景。
reentrantlock概述与基本特性
reentrantlock是java 5引入的显式锁机制,它基于aqs(abstractqueuedsynchronizer)框架实现,提供了比synchronized更丰富的功能和控制能力。与synchronized相比,reentrantlock具有以下显著特点:
- 可重入性:同一线程可以多次获得同一把锁而不会被阻塞,每次获取锁后必须释放相同次数的锁
- 公平性选择:支持公平锁和非公平锁两种策略,公平锁按照线程请求顺序分配锁,非公平锁允许"插队"以提高吞吐量
- 灵活的锁获取方式:提供尝试非阻塞获取锁(trylock)、可中断获取锁(lockinterruptibly)和超时获取锁(trylock with timeout)等方法
- 条件变量支持:通过condition接口实现多个等待队列,比synchronized的wait/notify机制更精准
从实现层级看,synchronized是jvm内置的锁机制,通过monitorenter/monitorexit字节码指令实现;而reentrantlock是jdk api级别的锁,基于aqs框架构建。
reentrantlock核心方法与使用
基础锁操作
reentrantlock的基本使用模式遵循"加锁-操作-释放锁"的流程,必须确保在finally块中释放锁:
reentrantlock lock = new reentrantlock(); lock.lock(); try { // 临界区代码 } finally { lock.unlock(); }
这种显式锁管理相比synchronized需要更多注意,但提供了更精细的控制。
高级锁获取方式
尝试非阻塞获取锁(trylock):
立即返回获取结果,不阻塞线程,适用于避免死锁或快速失败场景:
if (lock.trylock()) { try { // 临界区代码 } finally { lock.unlock(); } } else { // 执行备选方案 }
超时获取锁:
在指定时间内尝试获取锁,避免无限期等待:
if (lock.trylock(2, timeunit.seconds)) { try { // 临界区代码 } finally { lock.unlock(); } }
可中断获取锁(lockinterruptibly):
允许在等待锁的过程中响应中断信号:
try { lock.lockinterruptibly(); try { // 临界区代码 } finally { lock.unlock(); } } catch (interruptedexception e) { // 处理中断 }
锁状态查询
reentrantlock提供了一系列状态查询方法:
islocked()
:查询锁是否被持有isheldbycurrentthread()
:当前线程是否持有锁getholdcount()
:当前线程持有锁的次数(重入次数)getqueuelength()
:等待获取锁的线程数
reentrantlock实现原理
aqs框架基础
reentrantlock的核心实现依赖于abstractqueuedsynchronizer(aqs),这是一个用于构建锁和同步器的框架。aqs内部维护了:
- volatile int state:同步状态,对于reentrantlock,0表示未锁定,>0表示锁定状态及重入次数
- fifo线程等待队列:管理获取锁失败的线程
公平锁与非公平锁实现
reentrantlock通过两种不同的sync子类实现锁策略:
非公平锁(默认):
final void lock() { if (compareandsetstate(0, 1)) // 直接尝试抢占 setexclusiveownerthread(thread.currentthread()); else acquire(1); }
新请求的线程可以直接插队尝试获取锁,不考虑等待队列
公平锁:
protected final boolean tryacquire(int acquires) { if (!hasqueuedpredecessors() && // 检查是否有前驱节点 compareandsetstate(0, acquires)) { setexclusiveownerthread(thread.currentthread()); return true; } // 可重入逻辑... }
严格按照fifo顺序分配锁,避免饥饿现象
锁的获取与释放流程
加锁过程:
- 尝试通过cas修改state状态
- 成功则设置当前线程为独占线程
- 失败则构造node加入clh队列尾部,并阻塞线程
释放过程:
- 减少持有计数(state减1)
- 当state为0时完全释放锁
- 唤醒队列中的下一个等待线程
reentrantlock实战应用
生产者-消费者模型
使用reentrantlock配合condition实现高效的生产者-消费者模式:
public class boundedbuffer { private final reentrantlock lock = new reentrantlock(); private final condition notfull = lock.newcondition(); private final condition notempty = lock.newcondition(); private final object[] items = new object[100]; private int putptr, takeptr, count; public void put(object x) throws interruptedexception { lock.lock(); try { while (count == items.length) notfull.await(); // 等待"不满"条件 items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notempty.signal(); // 通知"不空"条件 } finally { lock.unlock(); } } public object take() throws interruptedexception { lock.lock(); try { while (count == 0) notempty.await(); // 等待"不空"条件 object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notfull.signal(); // 通知"不满"条件 return x; } finally { lock.unlock(); } } }
这种实现比synchronized+wait/notify更高效,因为可以精准唤醒生产者或消费者线程。
银行转账避免死锁
使用trylock实现带超时的转账操作,避免死锁:
public boolean transfer(account from, account to, int amount, long timeout, timeunit unit) { long stoptime = system.nanotime() + unit.tonanos(timeout); while (true) { if (from.getlock().trylock()) { try { if (to.getlock().trylock()) { try { if (from.getbalance() < amount) throw new insufficientfundsexception(); from.withdraw(amount); to.deposit(amount); return true; } finally { to.getlock().unlock(); } } } finally { from.getlock().unlock(); } } if (system.nanotime() > stoptime) return false; thread.sleep(fixeddelay); } }
通过trylock和超时机制,有效预防了死锁风险。
可中断的任务执行
使用lockinterruptibly实现可中断的任务执行:
public class interruptibletask { private final reentrantlock lock = new reentrantlock(); public void executetask() throws interruptedexception { lock.lockinterruptibly(); try { // 执行可能长时间运行的任务 while (!thread.currentthread().isinterrupted()) { // 任务逻辑... } } finally { lock.unlock(); } } }
这种模式适用于需要支持任务取消的场景。
reentrantlock与synchronized的对比
特性 | synchronized | reentrantlock |
---|---|---|
实现层级 | jvm内置 | jdk api实现 |
锁释放 | 自动 | 必须手动调用unlock() |
公平锁支持 | 仅非公平 | 支持公平和非公平策略 |
可中断获取锁 | 不支持 | 支持(lockinterruptibly) |
超时获取锁 | 不支持 | 支持(trylock with timeout) |
条件变量 | 单一等待队列 | 支持多个condition |
锁状态查询 | 有限 | 提供丰富查询方法 |
性能 | java 6+优化 | 复杂场景下表现更好 |
代码简洁性 | 高 | 较低(需手动管理) |
适用场景 | 简单同步 | 复杂同步需求 |
在java 6及以后版本中,synchronized经过锁升级(偏向锁→轻量级锁→重量级锁)优化,性能与reentrantlock差距已不明显。因此,简单场景推荐使用synchronized,复杂场景才考虑reentrantlock。
reentrantlock最佳实践
始终在finally块中释放锁:
确保锁一定会被释放,避免死锁:
lock.lock(); try { // 临界区代码 } finally { lock.unlock(); }
避免嵌套锁:
尽量不要在持有一个锁的情况下尝试获取另一个锁,容易导致死锁。
合理选择锁策略:
- 高吞吐场景:非公平锁(默认)
- 避免饥饿场景:公平锁
优先使用trylock:
特别是涉及多个锁的操作,使用trylock可以避免死锁。
合理使用condition:
替代object的wait/notify,实现更精准的线程通信。
性能考量:
简单同步场景优先选择synchronized,复杂场景才使用reentrantlock。
总结
reentrantlock作为java并发编程中的重要工具,通过其可重入性、公平性选择、灵活的锁获取方式和条件变量支持,为开发者提供了比synchronized更强大的线程同步能力。理解其基于aqs的实现原理,掌握各种高级特性的使用方法,并遵循最佳实践,可以帮助我们构建更高效、更健壮的并发程序。在实际开发中,应根据具体场景需求,在synchronized和reentrantlock之间做出合理选择。
到此这篇关于java reentrantlock的使用与应用实战的文章就介绍到这了,更多相关java reentrantlock内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论