Java并发编程:详解ReentrantLock互斥锁的用法与实践

追风的人
追风的人
发布时间:2026-10-13| 3人浏览过
原创

本文深入解析 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并发控制资料请关注本站其它相关文章!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。