作者没有四次元口袋的蓝胖日期2026-07-06标签Java, 多线程, ReentrantLock, AQSJava多线程知识梳理(3)一、ReentrantLock 原理ReentrantLock可重入锁是 Java 并发包JUC中的核心类基于AQSAbstractQueuedSynchronizer实现功能比 synchronized 丰富得多。1.1 什么是可重入重入同一线程在外层获取锁后在内层再次获取同一把锁时不会死锁而是直接成功。ReentrantLocklocknewReentrantLock();publicvoidmethodA(){lock.lock();// 外层获取锁try{methodB();// 调用 B}finally{lock.unlock();// 外层释放锁}}publicvoidmethodB(){lock.lock();// 内层再次获取同一把锁 —— 如果不可重入就会死锁try{System.out.println(B);}finally{lock.unlock();// 必须成对释放}}ReentrantLock 通过维护一个state变量AQS 中的实现可重入第一次获取锁state 0 → 1同一线程重入state 1 → 2 → 3…每次释放锁state 递减到 0 时真正释放注意synchronized 也是可重入的通过对象头的 Mark Word 记录持有线程ID和重入次数。1.2 AQS 核心原理简述AQS 是 ReentrantLock 的底层骨架核心结构AQS ├── state同步状态int类型 ├── CLH 队列双向链表存储等待的线程 │ └── Node { thread, waitStatus, prev, next } └── 核心方法acquire()、release()、tryAcquire()、tryRelease()工作流程线程尝试获取锁失败则包装成 Node 加入 CLH 队列每个 Node 通过自旋或 park阻塞等待前驱节点的释放锁释放时唤醒队列中的下一个节点1.3 公平锁 vs 非公平锁ReentrantLockfairLocknewReentrantLock(true);// 公平锁ReentrantLockunfairLocknewReentrantLock(false);// 非公平锁默认对比公平锁非公平锁获取顺序严格按 FIFO 顺序允许插队CAS 尝试获取性能较低要维护队列顺序较高减少线程切换饥饿不会新线程可能抢到锁导致等待久的线程延迟实现区别获取锁时必须检查队列中是否有等待线程直接 CAS 抢锁抢不到才进队列非公平锁获取流程线程来了直接 CAS 尝试抢锁不排队抢到 → 返回成功抢不到 → 检查 state如果是可重入则直接获取否则加入 CLH 队列尾部等待公平锁获取流程线程来了先检查队列是否为空队列不为空 → 老老实实排到队尾队列为空 → 才尝试 CAS 抢锁 绝大多数场景用非公平锁性能更好。公平锁只有在对响应时间公平性有严格要求时才使用。1.4 ReentrantLock 的常用方法方法说明lock()阻塞式获取锁不可中断lockInterruptibly()可中断获取锁等待时响应中断tryLock()非阻塞尝试获取锁立即返回 true/falsetryLock(long time, TimeUnit unit)指定时间内尝试获取超时返回 falseunlock()释放锁必须在 finally 中调用isLocked()查询当前是否有线程持有锁getHoldCount()查询当前线程重入次数1.5 正确用法模板ReentrantLocklocknewReentrantLock();publicvoiddoSomething(){lock.lock();// 必须在 try 之外try{// 临界区代码}finally{lock.unlock();// 必须 finally 释放否则异常时永远锁死}}// 可中断版本publicvoiddoSomethingInterruptible(){try{lock.lockInterruptibly();try{// 临界区代码}finally{lock.unlock();}}catch(InterruptedExceptione){Thread.currentThread().interrupt();}}⚠️常见坑lock() 放在 try 里如果 lock() 本身抛异常finally 中的 unlock() 会释放一个没被持有的锁导致 IllegalMonitorStateException。二、ReentrantLock vs synchronized 深度对比对比维度synchronizedReentrantLock实现层面JVM 关键字monitorenter/monitorexitJDK API基于 AQS锁释放JVM 自动释放必须手动 unlock()放在 finally可中断不可中断阻塞等可中断lockInterruptibly超时获取不支持tryLock(time)尝试获取不支持tryLock() 非阻塞公平性非公平可选公平/非公平条件变量只有一个Object 的 wait/notify可绑定多个 Condition锁升级有无锁→偏向→轻量级→重量级无直接重量级性能JDK6 后优化锁升级略低于优化后的 synchronized代码简洁性简洁关键字即可繁琐必须 try-finally2.1 什么时候选谁选 synchronized简单的同步代码块/方法对性能要求不高、代码简洁优先不需要特殊锁功能选 ReentrantLock需要公平锁需要响应中断 / 超时获取需要 tryLock 非阻塞尝试需要多个 Condition如生产者-消费者模式需要细粒度的锁控制 阿里 Java 开发手册强制要求在使用锁时优先选择 Lock 接口ReentrantLock因为其功能更丰富。2.2 Condition更细粒度的条件控制ReentrantLocklocknewReentrantLock();ConditionnotFulllock.newCondition();// 生产者等待ConditionnotEmptylock.newCondition();// 消费者等待// 生产者lock.lock();try{while(isFull())notFull.await();// 满则等待produce();notEmpty.signal();// 生产后通知消费者}finally{lock.unlock();}// 消费者lock.lock();try{while(isEmpty())notEmpty.await();// 空则等待consume();notFull.signal();// 消费后通知生产者}finally{lock.unlock();}Object 的 wait/notify/notifyAll 只能配合单一锁而 Condition 可以有多个实现更精细的线程调度如 ArrayBlockingQueue 的实现。三、思维导图速览┌───────────────────────────────────────────────────┐ │ ReentrantLock 深度解析 │ ├───────────────────────────────────────────────────┤ │ │ │ 底层AQSAbstractQueuedSynchronizer │ │ • state同步状态int │ │ • CLH 队列双向链表存储等待线程 │ │ │ │ 可重入实现 │ │ • state 计数0→1→2... 释放时递减 │ │ • synchronized 也可重入Mark Word 记录 │ │ │ │ 公平锁 vs 非公平锁 ⭐⭐⭐⭐⭐ │ │ • 公平锁先检查队列严格按FIFO │ │ • 非公平锁直接CAS抢性能更好 │ │ │ │ 常用方法 │ │ • lock / lockInterruptibly │ │ • tryLock / tryLock(time) │ │ • unlock必须 finally │ │ │ │ Condition多条件变量 ⭐⭐⭐⭐ │ │ • 比 wait/notify 更精细 │ │ • 典型生产者-消费者notFull/notEmpty │ │ │ │ vs synchronized ⭐⭐⭐⭐⭐ │ │ • 实现JVM vs JDK │ │ • 释放自动 vs 手动 │ │ • 功能简单 vs 丰富中断/超时/公平/多Condition│ └───────────────────────────────────────────────────┘四、写在最后AQS 是 JUC 的核心骨架理解 AQS 才能理解 ReentrantLock、CountDownLatch、Semaphore 等所有并发工具后续学习 CountDownLatch 时会更容易ReentrantLock vs synchronized是面试高频对比题要能清晰说出 6-8 个对比维度lock() 必须放在 try 之外这个细节很容易被忽略手写代码时要注意