1. 回顾:JMM 三大核心问题

我们在前半部分解决了并发编程的基础难题:

  • 可见性 (Visibility):用 volatile 解决。

  • 原子性 (Atomicity):用 synchronized、CAS 或 Atomic 类解决。

  • 有序性 (Ordering):用 volatile (Happens-Before 原则) 解决。


2. 为什么我们需要 ReentrantLock

虽然 synchronized 也是一种可重入锁,但它在处理复杂并发场景时显得过于“死板”。

synchronized 的局限性(“身不由己”):

  • 无法中断:一旦进入阻塞队列,就无法响应中断信号,必须死等。

  • 无法超时:不能设置等待时间(如“只等5秒”)。

  • 无法判断:无法知道锁的状态或队列情况。

  • 阻塞机制:没抢到锁的线程会进入 阻塞态 (Blocked),虽然不消耗 CPU,但唤醒成本高。

ReentrantLock 的“超能力”:

  • 灵活拿锁

    • tryLock():尝试一下,拿不到就走(避免死锁)。

    • tryLock(time):设置超时时间(快速失败)。

    • lockInterruptibly():等待时可以被中断(响应取消操作)。

  • 异常处理:必须遵循 try-finally 模式,确保在 finally 块中释放锁 (unlock())。


3. 公平锁 vs. 非公平锁 

ReentrantLock 构造函数可以指定 fair 参数。

  • 公平锁 (Fair):严格遵守“先来后到”排队。

    • 缺点:性能较差,因为频繁挂起和唤醒线程会带来昂贵的上下文切换开销。

  • 非公平锁 (Unfair)默认策略。允许新来的线程“插队”抢锁。

    • 原理:利用唤醒队列头部线程的“时间差”,让正处于运行态的新线程先执行,提高吞吐量。

    • 代价:极端情况下可能导致队列里的线程 饥饿 (Starvation)


4. 锁的引擎:AQS (AbstractQueuedSynchronizer) 

它是 Java 并发包的基石,核心是一个 volatile int state 变量。

State 的含义(作为计数器):

  • State = 0: 锁是自由的,无人持有。

  • State = 1: 锁被某个线程持有了。

  • State > 1: 可重入 (Reentrancy) 发生。

    • 同一个线程多次获得同一把锁,state 累加。

    • 解锁逻辑:“谁上的锁谁解”,调用 unlock()state 减 1。

    • 只有当 state 减回到 0 时,锁才算彻底释放,AQS 才会唤醒下一个等待的线程。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐