👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中... 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
📚欢迎订阅专栏,专栏名《在2B工作中寻求并发是否搞错了什么》
前言
马上跟上主播的节奏,看看怎么使用ReentrantLock,这个比较简单,非常适合我们新人朋友奥!
本文只介绍简单的使用,具体原理,主播下一篇具体研究🤗
什么是ReentrantLock?
ReentrantLock
是 Java 并发包 (java.util.concurrent.locks
) 中的一个类,它实现了 Lock
接口,提供了与 synchronized
关键字类似的互斥锁功能,但具有更高的灵活性和扩展性。
主要特点:
可重入性:
- 与
synchronized
一样,ReentrantLock
是可重入的。这意味着同一个线程可以多次获取同一个锁,而不会导致死锁。- 每次获取锁后,必须相应地进行相同次数的释放。
- 与
公平性:
ReentrantLock
支持公平锁和非公平锁。公平锁会按照线程请求锁的顺序来分配锁,而非公平锁则允许插队,可能导致某些线程长时间无法获取锁。- 通过构造函数可以指定是否使用公平锁,默认是非公平锁。
可中断的锁获取:
ReentrantLock
提供了lockInterruptibly()
方法,允许线程在等待锁的过程中响应中断。
超时获取锁:
tryLock()
方法可以尝试获取锁,如果锁不可用,可以选择立即返回或等待一段时间后返回。
条件变量:
ReentrantLock
提供了newCondition()
方法,可以创建Condition
对象,用于更细粒度的线程通信和等待/通知机制。
ReentrantLock可以怎么用?
基本使用锁
作用: 阻塞等待
- 非公平锁(默认) :直接尝试通过 CAS(Compare-And-Swap)抢占锁。若失败,则进入等待队列(AQS 队列),自旋或挂起线程。
- 公平锁:直接进入等待队列,严格按 FIFO 顺序分配锁。
import java.util.concurrent.locks.ReentrantLock;
ReentrantLock lock = new ReentrantLock(); // 默认非公平锁
// ReentrantLock lock = new ReentrantLock(true); // 公平锁
public void doSomething() {
lock.lock(); // 阻塞直到获取锁
try {
// 临界区代码
} finally {
lock.unlock(); // 必须手动释放!
}
}
尝试获取锁
作用: 非阻塞式尝试获取锁,立即返回成功或失败状态,避免线程无限等待。
场景:
- 需要快速失败逻辑(如资源被占用时执行其他操作)
- 避免死锁(如同时获取多个锁时)
if (lock.tryLock()) { // 立即尝试,不阻塞
try { /* 操作 */ } finally { lock.unlock(); }
} else { /* 失败处理 */ }
超时获取锁
作用: 在指定时间内等待获取锁,超时后放弃,避免无限阻塞。
场景:
- 需要控制最长等待时间(如响应实时性要求高的系统)
- 避免因死锁导致线程长期挂起
if (lock.tryLock(1, TimeUnit.SECONDS)) { // 最多等待1秒
try { /* 操作 */ } finally { lock.unlock(); }
} else { /* 超时处理 */ }
可中断获取
作用: 在等待锁的过程中响应线程中断,避免无法取消的阻塞。
场景:
- 需要支持任务取消(如用户主动取消长时间操作)
- 避免线程被永久阻塞
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockInterruptExample {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
// 线程1:先获取锁并执行长时间任务
Thread t1 = new Thread(() -> {
lock.lock();
try {
System.out.println("线程1获取锁,开始执行5秒任务...");
Thread.sleep(5000); // 模拟长时间任务
System.out.println("线程1任务完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("线程1释放锁");
}
});
// 线程2:尝试可中断获取锁
Thread t2 = new Thread(() -> {
try {
System.out.println("线程2尝试获取锁(可中断)...");
lock.lockInterruptibly(); // 可中断获取锁
try {
System.out.println("线程2成功获取锁!");
} finally {
lock.unlock();
System.out.println("线程2释放锁");
}
} catch (InterruptedException e) {
System.out.println("线程2在等待锁时被中断!");
}
});
t1.start();
Thread.sleep(1000); // 确保t1先获取锁
t2.start();
Thread.sleep(1000); // 等待t2进入阻塞状态
t2.interrupt(); // 中断t2的等待
}
}
条件变量
作用: 通过 Condition
实现线程间的精确协调,替代 Object.wait/notify
,支持多个等待队列。
场景:
- 生产者-消费者模型
- 线程按条件执行(如任务队列非空时唤醒消费者)
Condition condition = lock.newCondition();
// 线程A:等待条件
lock.lock();
try {
while (!conditionMet) {
condition.await(); // 释放锁并等待(类似 wait())
}
} finally { lock.unlock(); }
// 线程B:通知条件
lock.lock();
try {
conditionMet = true;
condition.signal(); // 唤醒一个线程(类似 notify())
// condition.signalAll(); // 唤醒所有等待线程
} finally { lock.unlock(); }
总结
ReentrantLock优点
灵活锁控制
- 支持
tryLock()
非阻塞获取锁。- 支持
tryLock(timeout)
超时获取锁。 - 支持
lockInterruptibly()
可中断获取锁。
- 支持
- 支持
公平性可选
- 默认是非公平锁(性能更高)。
- 可通过构造方法选择公平锁(按等待顺序分配锁)。
- 默认是非公平锁(性能更高)。
多条件变量
- 通过
newCondition()
创建多个Condition
,实现精细的线程协调(如生产者-消费者模型)。
- 通过
可重入性
- 同一线程可多次获取锁,避免死锁(需匹配相同次数的
unlock()
)。
- 同一线程可多次获取锁,避免死锁(需匹配相同次数的
锁状态监控
- 提供
getHoldCount()
、isLocked()
等方法,方便调试和监控锁状态。
- 提供
ReentrantLock缺点
手动管理锁
- 必须显式调用
lock()
和unlock()
,遗漏unlock()
会导致死锁。
- 必须显式调用
代码复杂性
- 相比
synchronized
,代码更冗长(需配合try-finally
)。
- 相比
性能开销
- 低竞争场景下性能略低于
synchronized
(因涉及 API 调用和 JUC 层逻辑)。
- 低竞争场景下性能略低于
错误使用风险
- 重入次数不匹配、嵌套锁逻辑复杂等问题易引发 Bug。
适用场景
需要高级锁功能
- 超时获取锁(避免死锁)。
- 可中断锁(支持任务取消)。
- 尝试获取锁(快速失败)。
- 超时获取锁(避免死锁)。
公平锁需求
- 需按线程请求顺序分配锁(如计费系统、任务调度)。
复杂线程协调
- 使用多个
Condition
实现生产者-消费者、任务队列等复杂协作逻辑。
- 使用多个
跨方法锁控制
- 锁在多个方法中嵌套使用(如递归调用或分布式锁模拟)。
锁状态监控
- 需要调试锁竞争情况(如监控等待线程数、锁持有状态)。
后话
什么?这就结束了,不要着急,主播正在快码加鞭的写原理。
小手手,点上关注。
主播马上一起和你们探索ReertrantLock原理😘