说到重入锁可能很多人第一反应就是一个线程重复获取同一把锁不会卡死。但这里面的实现细节挺有意思的。独占锁的重入同一时刻只有一个线程能持有锁要实现重入只需要解决两个问题现在是谁拿着锁这个线程拿了几层常见有两种记法。显式计数最直接的做法就是找个变量记着。ReentrantLock和重量级Monitor用的都是这套owner 当前线程count简单粗暴但有效。隐式计数栈结构synchronized的轻量级锁路径有点不一样。它不单独存一个整数而是利用栈上的Lock Record。第一次进入同步块时线程会在栈上分配一个真正的Lock Record里面保存着对象头的原始Mark Word。后续重入怎么办再压一个栈帧但这次是个空的Lock Record什么都不存。所以栈上有几个空记录就表示重入了几次。退出的时候弹出空记录 → 只是退出一层不释放锁弹出非空记录 → 最外层到了该释放锁这就好比递归调用栈天然帮你计数省掉了额外维护计数器的成本。为什么要这么优化如果不优化每次重入都走完整的加锁流程复制对象头CAS改对象头竞争锁状态但锁本来就是你自己的根本没人和抢再CAS来CAS去纯属浪费。JVM聪明的地方就在于第一次真正加锁后面只做个我又进来了的标记。共享锁的重入共享锁比独占锁复杂一点。同一时刻可以有多个线程一起持有所以不仅要记总共有多少人在用还要记当前线程自己拿了几次。因为释放的时候必须校验这个线程到底持有几次还剩几次没释放完。三级缓存优化最直接的办法是每个线程都去ThreadLocal里查自己的次数能工作但有额外开销。而读锁最常见的场景是只有一个线程在读或者同一个线程连续读。所以JDK搞了三级优化从快到慢第一层firstReader第一个读线程直接用两个字段记录firstReader和firstReaderHoldCount。第二次重入直接就行连ThreadLocal都不用查。第二层cachedHoldCounter如果不是第一个读线程就看当前线程是不是上一次成功获取读锁的线程。如果是直接用缓存的那个计数对象省一次ThreadLocal.get。第三层ThreadLocalHoldCounter前两层都没命中才走最终方案从ThreadLocal里拿。为什么叫避免全局竞争因为每个线程的持有次数放到ThreadLocal里每个线程只碰自己的局部数据减少了共享热点的竞争。当然state本身还是共享的获取释放时还是要CAS改它。这是两码事。小结重入计数看起来简单但要考虑性能就各有各的玩法独占锁可以用显式count也可以用栈结构隐式计数共享锁要额外记每个线程自己的次数于是搞出了三级缓存