前言:
在 Java 并发编程领域,线程安全始终是核心关注点之一。synchronized关键字作为 JVM 层面的内置锁,曾长期是解决线程安全问题的主要选择。但随着并发场景复杂度提升,其灵活性不足、功能单一的缺陷逐渐凸显。java.util.concurrent.locks.ReentrantLock的出现,为开发者提供了更强大、更灵活的锁机制,成为并发编程中的重要工具。本文将从概念、原理、特性、实践等维度,全面解析 ReentrantLock,助力开发者深入掌握其使用方法与设计思想。
一、什么是可重入锁?从概念到本质
1.可重入锁的定义
可重入锁(Reentrant Lock),又称递归锁,是指同一个线程可以多次获取同一把锁,且不会因自身已持有该锁而产生阻塞的锁机制。这种特性允许线程在持有锁的情况下,再次进入由该锁保护的代码块,无需担心死锁问题。ReentrantLock 是 Java 中可重入锁的典型实现,它实现了java.util.concurrent.locks.Lock接口,不仅具备可重入特性,还扩展了诸多synchronized不具备的高级功能,如公平锁控制、中断支持、超时获取等。
2.可重入特性的底层逻辑
ReentrantLock 的可重入性并非 “自动生效”,而是通过底层状态管理实现的。其核心依赖AbstractQueuedSynchronizer(AQS,抽象队列同步器)的状态变量(state) :
当线程首次获取锁时,AQS 会将state从 0 修改为 1,并记录当前持有锁的线程(exclusiveOwnerThread)。
若该线程再次请求获取同一把锁,AQS 会检测到当前线程就是锁的持有者,此时仅将state的值加 1(即 “重入计数 + 1”),无需重新竞争锁。
线程释放锁时,每调用一次unlock(),state的值就减 1;只有当state减至 0 时,锁才会真正释放,其他线程才能竞争。
二、为什么需要 ReentrantLock
1. 同时支持公平锁与非公平锁
公平锁:锁的分配遵循 “先到先得” 原则,等待时间最长的线程优先获取锁,避免线程饥饿(某线程长期无法获取锁)。
非公平锁:线程请求锁时,会先尝试直接抢占锁(无视等待队列),只有抢占失败才会加入等待队列。非公平锁的并发性能更高(减少队列唤醒开销),是 ReentrantLock 的默认策略。
ReentrantLock 通过构造函数指定锁类型:
// 非公平锁(默认)
ReentrantLock nonFairLock = new ReentrantLock();
// 公平锁(显式指定)
ReentrantLock fairLock = new ReentrantLock(true);
2.支持中断式获取锁
ReentrantLock 的lockInterruptibly()方法允许线程在等待锁时响应中断:若其他线程调用该线程的interrupt()方法,等待锁的线程会抛出InterruptedException,并终止等待,避免线程无限阻塞。
import java.util.concurrent.locks.ReentrantLock;
public class InterruptibleLockDemo {
private final ReentrantLock lock = new ReentrantLock();
public void doTask() {
try {
// 支持中断的锁获取
lock.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "获取锁,开始执行任务");
// 模拟任务执行(耗时)
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "在等待锁时被中断");
Thread.currentThread().interrupt(); // 恢复中断状态,供上层处理
} finally {
// 确保锁释放(需先判断当前线程是否持有锁)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
}
public static void main(String[] args) throws InterruptedException {
InterruptibleLockDemo demo = new InterruptibleLockDemo();
// 线程1先获取锁
Thread thread1 = new Thread(demo::doTask, "Thread-1");
thread1.start();
Thread.sleep(1000); // 确保thread1先持有锁
// 线程2尝试获取锁,随后被中断
Thread thread2 = new Thread(demo::doTask, "Thread-2");
thread2.start();
Thread.sleep(1000);
thread2.interrupt(); // 中断thread2的锁等待
}
}
3.支持超时获取锁
ReentrantLock 的tryLock(long timeout, TimeUnit unit)方法允许线程在指定时间内尝试获取锁:若在超时时间内成功获取锁,返回true;若超时仍未获取锁,返回false,线程可自行处理(如放弃任务、重试等),避免无限阻塞。这一特性在 “避免死锁”“控制任务执行时效” 场景中非常实用。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TimeoutLockDemo {
private final ReentrantLock lock = new ReentrantLock();
public void tryAcquireLockWithTimeout() {
try {
// 尝试在3秒内获取锁
boolean isLocked = lock.tryLock(3, TimeUnit.SECONDS);
if (isLocked) {
System.out.println(Thread.currentThread().getName() + "成功获取锁");
// 模拟任务执行
Thread.sleep(2000);
} else {
System.out.println(Thread.currentThread().getName() + "超时未获取锁,放弃任务");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放锁");
}
}
}
public static void main(String[] args) {
TimeoutLockDemo demo = new TimeoutLockDemo();
// 线程1先获取锁,执行2秒
new Thread(demo::tryAcquireLockWithTimeout, "Thread-1").start();
// 线程2后尝试获取锁,超时时间3秒(实际等待2秒后成功)
new Thread(demo::tryAcquireLockWithTimeout, "Thread-2").start();
}
}
4.支持多个条件变量(Condition)
ReentrantLock 通过newCondition()方法创建多个Condition对象,每个Condition对应一个独立的等待队列,支持 “精准唤醒”:线程通过condition.await()进入该 Condition 的等待队列;其他线程通过condition.signal()/signalAll()唤醒该队列中的线程,避免无效竞争。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionDemo {
// 用Condition实现“生产者-消费者”中的精准唤醒(先生产后消费)
private final ReentrantLock lock = new ReentrantLock();
private final Condition producerCond = lock.newCondition(); // 生产者等待队列
private final Condition consumerCond = lock.newCondition(); // 消费者等待队列
private int count = 0;
private final int MAX_COUNT = 5; // 队列最大容量
// 生产者方法:生产数据
public void produce() throws InterruptedException {
lock.lock();
try {
// 若队列已满,生产者进入等待
while (count >= MAX_COUNT) {
System.out.println("队列已满,生产者" + Thread.currentThread().getName() + "等待");
producerCond.await(); // 生产者进入producerCond等待队列
}
// 生产数据
count++;
System.out.println("生产者" + Thread.currentThread().getName() + "生产,当前 count=" + count);
// 唤醒消费者(队列有数据了)
consumerCond.signal();
} finally {
lock.unlock();
}
}
// 消费者方法:消费数据
public void consume() throws InterruptedException {
lock.lock();
try {
// 若队列为空,消费者进入等待
while (count <= 0) {
System.out.println("队列为空,消费者" + Thread.currentThread().getName() + "等待");
consumerCond.await(); // 消费者进入consumerCond等待队列
}
// 消费数据
count--;
System.out.println("消费者" + Thread.currentThread().getName() + "消费,当前 count=" + count);
// 唤醒生产者(队列有空间了)
producerCond.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ConditionDemo demo = new ConditionDemo();
// 启动2个生产者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
while (true) {
try {
demo.produce();
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "P" + i).start();
}
// 启动2个消费者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
while (true) {
try {
demo.consume();
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "C" + i).start();
}
}
}
5.支持锁状态检测
ReentrantLock 提供了多个方法用于查询锁的状态,方便开发者调试与监控:
isHeldByCurrentThread():判断当前线程是否持有该锁;isLocked():判断锁是否被任何线程持有;getHoldCount():获取当前线程持有该锁的重入次数;getQueueLength():获取等待获取锁的线程数(近似值);hasQueuedThreads():判断是否有线程在等待获取锁。
三、ReentrantLock 的核心操作
1.锁获取与释放方法
方法说明void lock()获取锁,若锁被占用则阻塞void lockInterruptibly()获取锁,允许在等待时被中断boolean tryLock()非阻塞尝试获取锁,立即返回结果boolean tryLock(long timeout, TimeUnit unit)在指定时间内尝试获取锁void unlock()释放锁,通常放在 finally 块中确保执行
2.状态查询方法
方法说明boolean isHeldByCurrentThread()当前线程是否持有该锁boolean isLocked()锁是否被任何线程持有boolean isFair()是否为公平锁
3.条件变量方法
Condition 方法说明void await()当前线程释放锁并进入等待状态,直到被其他线程唤醒或中断void signal()唤醒在此Condition上等待的一个线程void signalAll()唤醒在此Condition上等待的所有线程
四、ReentrantLock 的内部结构
ReentrantLock 通过三个内部类实现锁机制:
1.Sync
继承自 AbstractQueuedSynchronizer(AQS),是公平锁与非公平锁的基类。
tryAcquire(int arg):尝试获取锁(由 FairSync 和 NonfairSync 分别实现公平 / 非公平逻辑);
tryRelease(int arg):尝试释放锁(统一实现,核心是将 AQS 的 state 减 1,直至为 0 时释放锁);
newCondition():创建 Condition 对象(基于 AQS 的 ConditionObject 实现)。
2.NonfairSync
非公平锁实现,其核心逻辑是 “先抢占,再排队”。
线程请求锁时,先尝试通过compareAndSetState(0, 1)直接修改 AQS 的 state(从 0 到 1),若成功则获取锁;
若抢占失败(state 已为 1),判断当前线程是否为锁的持有者:若是,则 state 加 1(重入);
若抢占失败且非持有者,则调用 AQS 的acquire(1),将线程加入等待队列并阻塞。
3.FairSync
公平锁实现,其核心逻辑是 “先排队,再获取”
线程请求锁时,先检查 AQS 的等待队列是否有线程在等待(hasQueuedPredecessors());
若有等待线程,则直接加入队列,不抢占;
若无线程等待,则尝试修改 state 获取锁;若 state 已为 1,再判断是否为当前线程(重入)。
五、synchronized 与 ReentrantLock 的区别
对比维度synchronizedReentrantLock锁的实现JVM 层面的内置锁(监视器锁)JDK 层面的锁(基于 AQS 实现)锁获取 / 释放隐式(JVM 自动处理:进入同步块获取,退出释放)显式(手动调用lock()/unlock(),需在finally中释放)可中断性不可中断(等待锁时无法响应中断)可中断(lockInterruptibly()支持中断)超时机制不支持支持(tryLock(timeout, unit))公平性仅支持非公平锁(无法配置)支持公平 / 非公平锁(构造函数指定)条件变量仅 1 个(基于对象监视器,wait()/notify())支持多个(newCondition()创建,精准唤醒)锁状态检测不支持(无 API 查询锁状态)支持(isLocked()/isHeldByCurrentThread()等)性能JDK 1.6 后与 ReentrantLock 接近,简单场景更优复杂场景(中断、超时、多条件)更优,但有手动释放开销
六、总结
ReentrantLock 是 Java 并发编程中非常重要的工具,提供了比 synchronized 更丰富的锁控制机制。通过合理使用 ReentrantLock 及其条件变量,可以构建高效、可靠的多线程应用程序。在实际开发中,应根据具体场景选择是否使用公平锁、是否使用超时机制等,以优化系统性能与响应能力。