Administrator
Published on 2026-07-30 / 0 Visits
0

ReentrantLock 完整详解

#AI

ReentrantLock 完整详解

一、基础介绍

ReentrantLock 位于 java.util.concurrent.locks,是 JDK 提供的显式锁,替代传统 synchronized 隐式同步锁。

核心特性:可重入

同一线程可多次获取同一把锁,不会死锁;获取多少次,释放多少次。
synchronized 底层也是可重入锁,但 ReentrantLock 功能更强、更灵活。

核心对比 synchronized vs ReentrantLock

特性synchronizedReentrantLock
锁类型隐式锁,自动释放显式锁,必须手动 unlock()
可重入支持支持
公平锁仅非公平公平/非公平可切换
可中断等待不可中断lockInterruptibly() 支持中断
尝试获取锁tryLock() 非阻塞尝试
多条件等待1个等待队列(wait/notify)多个 Condition 等待队列
性能JDK1.6后优化,差距很小高并发场景灵活可控
语法代码块/方法try-finally 标准写法

二、两种锁模式:公平锁 / 非公平锁

构造函数控制模式:

// 1. 默认:非公平锁(吞吐量更高)
ReentrantLock lock = new ReentrantLock();

// 2. 传入true:公平锁,按线程等待顺序获取锁
ReentrantLock fairLock = new ReentrantLock(true);

1. 非公平锁(默认,推荐)

  • 线程抢锁不排队,新线程上来直接竞争;
  • 优势:上下文切换少,吞吐量高;
  • 缺点:可能存在线程饥饿(长期抢不到锁)。

2. 公平锁

  • 严格按照线程等待FIFO顺序获取锁;
  • 优势:无饥饿,公平;
  • 缺点:大量上下文切换,并发性能差,极少使用。

三、核心API方法

1. 获取锁

  1. void lock()
    阻塞获取锁,拿不到锁则无限等待,不响应中断
  2. void lockInterruptibly() throws InterruptedException
    阻塞等待锁,等待过程中线程被 interrupt() 中断,直接抛出异常,可处理中断。
  3. boolean tryLock()
    非阻塞尝试,立刻返回:拿到锁true,没拿到false,不等待。
  4. boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
    限时等待锁:指定时间内没拿到返回false;等待中可被中断。

2. 释放锁

void unlock()

  • 必须在 finally 执行,保证锁一定释放;
  • 可重入场景:加锁n次,必须释放n次,否则锁永久占用,死锁。

3. 可重入相关工具方法

// 当前线程持有锁次数
int getHoldCount()
// 判断当前线程是否持有锁
boolean isHeldByCurrentThread()
// 判断锁是否被任意线程占用
boolean isLocked()
// 公平锁:返回true是公平锁
boolean isFair()
// 返回等待该锁的线程数量(仅调试)
int getQueueLength()

4. 多条件等待 Condition

Condition newCondition()
一把锁可以创建多个等待队列,精准唤醒指定线程,比 wait/notify 强大很多。

  • condition.await():释放锁,阻塞等待;
  • condition.signal():唤醒一个等待线程;
  • condition.signalAll():唤醒全部。

四、标准使用模板(必看,防止死锁)

基础模板(lock())

ReentrantLock lock = new ReentrantLock();

public void test() {
    // 1. 获取锁
    lock.lock();
    try {
        // 同步业务逻辑
        System.out.println("执行业务");
    } finally {
        // 2. 必须finally释放,异常也能解锁
        lock.unlock();
    }
}

可中断锁 lockInterruptibly()

public void interruptTest() throws InterruptedException {
    lock.lockInterruptibly();
    try {
        // 业务
    } finally {
        lock.unlock();
    }
}

限时尝试锁 tryLock

public void tryLockTest() {
    // 等待2秒
    if (lock.tryLock(2, TimeUnit.SECONDS)) {
        try {
            // 获取锁成功,执行业务
        } finally {
            lock.unlock();
        }
    } else {
        // 2秒没拿到锁,降级处理
        System.out.println("获取锁失败,稍后重试");
    }
}

五、核心特性代码演示

1. 可重入特性演示

同一线程多次加锁,计数累加,释放次数匹配才能完全解锁:

ReentrantLock lock = new ReentrantLock();

public void reentrantDemo() {
    lock.lock();
    try {
        System.out.println("第1次持有次数:" + lock.getHoldCount()); // 1
        innerMethod();
    } finally {
        lock.unlock();
        System.out.println("释放1次,持有次数:" + lock.getHoldCount()); // 0
    }
}

private void innerMethod() {
    lock.lock();
    try {
        System.out.println("第2次持有次数:" + lock.getHoldCount()); // 2
    } finally {
        lock.unlock();
        System.out.println("释放1次,持有次数:" + lock.getHoldCount()); // 1
    }
}

错误场景:只lock两次,只unlock一次 → 锁永远占用,其他线程永久阻塞。

2. Condition 多条件精准等待(生产者消费者)

synchronized 只有一个等待队列,notify会随机唤醒;Condition可分区等待。

ReentrantLock lock = new ReentrantLock();
// 两个独立等待队列
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
List<String> queue = new ArrayList<>();
int maxSize = 5;

// 生产者
public void produce(String msg) throws InterruptedException {
    lock.lock();
    try {
        // 队列满,生产者等待
        while (queue.size() >= maxSize) {
            notFull.await();
        }
        queue.add(msg);
        System.out.println("生产:" + msg);
        // 唤醒消费者
        notEmpty.signal();
    } finally {
        lock.unlock();
    }
}

// 消费者
public String consume() throws InterruptedException {
    lock.lock();
    try {
        // 队列为空,消费者等待
        while (queue.isEmpty()) {
            notEmpty.await();
        }
        String msg = queue.remove(0);
        System.out.println("消费:" + msg);
        // 唤醒生产者
        notFull.signal();
        return msg;
    } finally {
        lock.unlock();
    }
}

六、底层原理(AQS 核心)

ReentrantLock 底层基于 AQS(AbstractQueuedSynchronizer) 实现同步:

  1. state 同步状态:int变量
    • state=0:无线程持有锁;
    • state>0:锁被占用,数值=可重入次数;
  2. 持有线程标记:记录当前独占锁的线程,实现可重入;
  3. CLH双向阻塞队列:抢锁失败的线程封装成Node,排队阻塞;
  4. 公平/非公平差异:
    • 非公平:线程上来直接CAS抢state,不管队列;
    • 公平:先判断队列是否有等待线程,有则排队,不抢占。

加锁简易流程(非公平lock())

  1. CAS尝试把 state 从0改为1,成功 → 获取锁,设置当前持有线程;
  2. CAS失败:判断当前线程是否是持有锁线程,是则 state+1(可重入);
  3. 都不满足:当前线程封装Node加入阻塞队列,自旋+park阻塞等待唤醒;
  4. 解锁:state-1,state归0后,唤醒队列头部等待线程。

七、常见坑 & 注意事项

  1. unlock() 必须放在finally
    业务代码抛出异常,不走finally会导致锁永不释放,其他线程永久阻塞。
  2. 不能跨线程释放锁
    A线程获取锁,B线程调用unlock直接抛 IllegalMonitorStateException
  3. 可重入必须成对 lock/unlock
    lock n次,必须 unlock n次,否则锁不会释放。
  4. await() 必须在锁内执行
    Condition等待方法必须在lock()~unlock()之间调用,否则报错。
  5. while循环判断等待条件,不要用if
    线程唤醒后可能被其他线程抢占资源,if会出现数据错乱,while循环重新校验。
  6. 公平锁性能差,业务极少使用
    只有强顺序要求场景才考虑公平锁,绝大多数业务默认非公平。
  7. tryLock 不要写死循环自旋
    无限while(tryLock())会疯狂占用CPU,建议加睡眠时间或超时时间。

八、适用场景

  1. 需要灵活中断等待锁的线程;
  2. 需要限时获取锁,避免无限阻塞;
  3. 需要精准分区等待(多Condition,生产者消费者、读写分离等);
  4. 需要控制锁公平性;
  5. 需要动态判断当前线程是否持有锁、获取重入次数等监控;
  6. 高并发复杂同步场景,synchronized 功能不足时。

九、同名任务串行执行拓展

同名任务串行、不同任务互不干扰,可以用 ConcurrentHashMap<String, ReentrantLock> 分组锁实现:

// key:任务分组名,value:分组独立可重入锁
private final Map<String, ReentrantLock> groupLockMap = new ConcurrentHashMap<>();

public void runTask(String group) {
    ReentrantLock lock = groupLockMap.computeIfAbsent(group, k -> new ReentrantLock());
    lock.lock();
    try {
        // 同组任务串行执行,其他分组互不影响
        System.out.println("执行分组任务:" + group);
    } finally {
        lock.unlock();
    }
}

优点:轻量、无额外线程池开销;缺点:大批量任务会阻塞调用线程,适合短时任务;长耗时任务推荐之前的分组单线程池方案。