Java并发编程:详解ReentrantLock互斥锁的用法与实践
本文深入解析 Java 中的 ReentrantLock 互斥锁。文章先对比 synchronized 隐式锁的区别,阐明 ReentrantLock 需显式释放锁的特性。随后通过三个递进的 Depot(仓库)示例,展示如何通过 lock/unlock 保证线程安全,并进一步结合 Condition 实现生产者/消费者模式下的线程等待与唤醒,最终解决库存越界和竞态条件问题。
1. 互斥锁与 ReentrantLock 基础
在 Java 1.5 之前,开发通常依赖 synchronized 关键字来控制对共享资源的访问。synchronized 使用隐式监视器锁,必须在同一个块结构中获取和释放锁,且顺序固定。而 java.util.concurrent.locks.ReentrantLock 提供了更灵活的锁定操作:
- 显式释放:必须手动调用
unlock()方法才能释放锁,这使得锁的获取和释放可以出现在不同的代码块中。 - 公平与非公平:分为“公平锁”(按 FIFO 队列排队获取)和“非公平锁”(默认策略,允许新到线程直接抢锁)。
- 可重入性:同一线程可以多次获取该锁,计数器随之增加,释放时递减。
核心 API 说明:
- lock() / unlock():获取与释放锁。建议将 unlock() 放在 finally 块中以防止死锁。
- tryLock(long timeout, TimeUnit unit):尝试在指定时间内获取锁,超时则返回 false。
- newCondition():创建与该锁关联的条件对象,用于线程间的通信(如生产/消费场景)。
2. ReentrantLock 实战:从互斥到线程协作
下面通过一个经典的“仓库模型”逐步演示 ReentrantLock 的应用。在该模型中,Depot 类管理库存,Producer 负责生产,Customer 负责消费。
第一步:构建基础的互斥访问(示例 1 vs 示例 2)
目的:验证独占锁对共享变量(仓库大小 size)保护的必要性。
风险提醒:若未正确释放锁或在异常时未释放,可能导致其他线程永久阻塞。
无锁情况(示例 2):当注释掉 lock.lock() 后,多个线程并发修改 size 会出现竞态条件,导致最终结果不正确(预期剩余50,实际可能为负数)。代码关键结构如下:
// 示例2:去掉锁后的非线程安全代码
public void produce(int val) {
// lock.lock(); // 缺失
size += val; // 发生数据竞争
// ... print ...
// lock.unlock(); // 缺失
}
有锁情况(示例 1):通过显式加锁,保证了操作原子性,避免了数据混乱:
// 示例1:标准的 lock/unlock 写法
public void produce(int val) {
lock.lock();
try {
size += val;
System.out.printf("%s produce(%d) --> size=%d\n",
Thread.currentThread().getName(), val, size);
} finally {
lock.unlock(); // 无论成功与否都释放锁
}
}
第二步:利用 Condition 解决业务逻辑限制(示例 3)
目的:引入仓库容量上限(Capacity)和防负数检查,利用 Condition 实现“库存满则停、库存空等”的逻辑,这是纯 synchronized + wait/notify 较难优雅实现的。
操作入口:Depot 类的构造函数初始化两个 Condition 对象。
this.fullCondtion = lock.newCondition(); // 表示库存在等待被消费的信号
this.emptyCondtion = lock.newCondition(); // 表示库存不足,等待生产的信号
动作细节:
- 生产流程:获取锁 -> 判断 size >= capacity,若满则调用 fullCondtion.await() 挂起当前线程 -> 补充库存 -> 调用 emptyCondtion.signal() 唤醒等待的消费者。
完成状态判断:只有当库存更新且在 try-finally 块正常结束时,锁才释放,其他线程方可继续执行。
public void produce(int val) {
lock.lock();
try {
int left = val;
while (left > 0) {
// 库存已满时,等待
while (size >= capacity)
fullCondtion.await();
// 计算本次实际增量
int inc = (size+left)>capacity ? (capacity-size) : left;
size += inc;
left -= inc;
// 通知消费者可以取货了
emptyCondtion.signal();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
} finally {
lock.unlock();
}
}
此方案完美解决了“示例1”中仓库容量无上限及可能变为负数的问题,确保了多线程环境下的业务合法性。
总结
ReentrantLock 相比 synchronized 提供了更高的灵活性,特别是在处理复杂的线程间通信(如 Condition)时。在实际使用中,务必遵循 try { lock(); ... } finally { unlock(); } 的黄金法则,避免死锁;同时根据并发冲突概率选择公平锁或非公平锁策略。
以上就是关于Java多线程编程中互斥锁ReentrantLock类的用法的详细内容,更多关于Java并发控制资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









