什么是锁的可重入性?

什么是锁的可重入性?

锁的“可重入性”(reentrancy)指:同一线程在已经获得某把锁的情况下,可以“再次”获得这把锁而不会被自己阻塞。实现上通常会为锁维护“持有线程 + 重入计数(hold count)”:同一线程每 lock()/进入一次同步块,计数+1;每次 unlock()/退出一次,计数−1;直到归零才真正释放。

为什么需要可重入?

很多代码会层层调用且每层都做同步保护。如果锁不可重入,同一线程第二次进入会卡死自己。可重入能让这类嵌套调用安全工作。

典型例子(可重入的内置锁 synchronized

public class ReentrantDemo {
  public synchronized void a() {  // 拿到 this 的监视器
    b();                          // 再次尝试进入同一把锁(this)
  }
  public synchronized void b() {  // 可重入:不会阻塞自己
    // ...
  }
}

a() 已经拿了 this 的监视器,调用 b() 时再次进入同一把锁,计数从 1 变 2,返回时各自退出一次,最终释放。

ReentrantLock 同理

ReentrantLock lock = new ReentrantLock();

void a() {
  lock.lock();
  try { b(); }
  finally { lock.unlock(); }   // 计数 -1
}
void b() {
  lock.lock();                 // 计数 +1(同一线程)
  try { /* ... */ }
  finally { lock.unlock(); }   // 计数 -1
}

还可以查询:

lock.isHeldByCurrentThread();  // 是否当前线程持有
lock.getHoldCount();           // 重入层数

不可重入会怎样?

自实现一把不可重入的锁(只记录“是否占用”,不记录“谁占用”和“重入计数”)会导致同线程二次进入自锁死

class NonReentrantLock {
  private volatile boolean locked = false;
  public synchronized void lock() throws InterruptedException {
    while (locked) wait();
    locked = true;
  }
  public synchronized void unlock() {
    locked = false;
    notify();
  }
}
NonReentrantLock l = new NonReentrantLock();
void a() throws Exception {
  l.lock();
  try { b(); } finally { l.unlock(); }
}
void b() throws Exception {
  l.lock();   // 同一线程再次 lock,发现 locked=true,自己在 wait(),→ 永久阻塞
  try { } finally { l.unlock(); }
}

这就是“不可重入”的危险。

可重入锁与“可重入代码”不是一回事

  • 可重入锁:同一线程可重复获得同一把锁。

  • 可重入(reentrant)代码/函数(计算机体系结构语境):函数在被多个线程/中断并发调用时不依赖共享可变状态,因而是线程安全/可重入的。这是不同概念。

常见锁的可重入性

  • synchronized(对象监视器/内置锁):✅ 可重入(JVM 维护持有者与计数)。

  • ReentrantLock:✅ 可重入(AQS 维护 owner 与 hold count,支持公平/非公平、可中断、可定时)。

  • Semaphore:❌ 不可重入(它是配额计数器,不跟线程绑定;同线程 acquire 多次会消耗多个许可,逻辑上不是“重入”)。

  • ReadWriteLock:实现视具体类(如 ReentrantReadWriteLock 的同线程读锁可重入,写锁对同线程也可重入且支持写降级为读)。

设计与使用要点

  1. 重入计数必须匹配释放次数
    每次成功 lock()/进入同步块都要对应一次 unlock()/退出;否则会漏释放(计数不归零,其他线程永远等)。

  2. 可重入不是“万能钥匙”
    它解决的是“同一线程的嵌套进入问题”,不能避免不同线程之间的死锁(交叉加锁顺序不一致照样死锁)。

  3. 层层调用的同步策略

  • 若上层已持有锁,下层方法就不要再对同一资源加另一把不同的锁(避免锁顺序复杂化)。

  • 如需多把锁,统一顺序获取,降低死锁概率。

  1. 诊断
  • ReentrantLock#getHoldCount() 有助于排查“忘记解锁”。

  • 线程 dump 可观察某线程在持有锁还在等待同一把锁时(不可重入实现下的自锁死)。

一句话记忆

可重入 = “同一线程可在未释放的情况下再次进入同一把锁”,靠“持有者 + 计数”实现;它防止“自己把自己锁死”,但并不自动避免多线程之间的死锁。

posted on 2025-11-10 02:25  滚动的蛋  阅读(5)  评论(0)    收藏  举报

导航