JDK Locks 设计及工作原理与教程 一、锁设计的哲学为什么需要锁在多线程环境下竞态条件Race Condition是万恶之源。锁的本质是提供一种内存可见性和操作原子性的保证。核心矛盾性能 vs. 安全。锁的设计就是在吞吐量和数据一致性之间寻找平衡点。二、JDK 锁体系架构全景图├── 悲观锁阻塞式 │ ├── synchronizedJVM内置锁 │ └── ReentrantLockAPI级锁 │ ├── 公平/非公平模式 │ └── 可中断/超时机制 ├── 乐观锁非阻塞式 │ └── CAS AtomicXXX硬件级支持 └── 读写锁分离 ├── ReentrantReadWriteLock └── StampedLockJDK 8乐观读三、synchronizedJVM 的亲儿子3.1 底层原理// 代码层面 public synchronized void method() { ... } // 字节码层面 // ACC_SYNCHRONIZED 标志位 → 进入 monitorenter → 退出 monitorexit3.2 锁升级过程JDK 1.6 优化无锁 → 偏向锁 → 轻量级锁CAS自旋 → 重量级锁OS互斥量关键设计思想偏向锁一个线程反复获取锁消除同步开销默认延迟4秒启用轻量级锁多线程交替执行使用CAS自旋避免线程阻塞重量级锁竞争激烈时挂起线程让出CPU3.3 实战示例public class SynchronizedDemo { private int counter 0; // 修饰实例方法锁是当前实例对象 public synchronized void increment() { counter; } // 修饰静态方法锁是Class对象 public static synchronized void staticMethod() { // ... } // 同步代码块更细粒度控制 public void blockLock() { Object lock new Object(); synchronized (lock) { // 临界区代码 } } }四、ReentrantLockAPI 级的全能选手4.1 核心设计架构ReentrantLock └── Sync继承AbstractQueuedSynchronizer ├── NonfairSync非公平默认 └── FairSync公平AQS 核心三要素statevolatile int表示锁状态0未占用0重入次数CLH队列双向链表存储等待线程CAS操作原子更新state4.2 关键源码解析// 非公平锁的 lock() 实现 final void lock() { // 上来先抢一次不管队列非公平核心 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); // 尝试获取失败则入队 } // acquire 流程模板方法模式 public final void acquire(int arg) { if (!tryAcquire(arg) acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }4.3 与 synchronized 对比特性synchronizedReentrantLock实现方式JVM内置Java API锁释放自动异常也释放需在finally中unlock()可中断性不支持lockInterruptibly()超时尝试不支持tryLock(timeout)公平性非公平支持公平/非公平条件等待wait/notifyCondition多路通知性能JDK 1.6接近已优化相近4.4 标准使用模式public class ReentrantLockDemo { private final ReentrantLock lock new ReentrantLock(true); // 公平锁 private final Condition condition lock.newCondition(); private int count 0; public void doWork() { lock.lock(); try { // 业务逻辑 count; condition.signalAll(); // 唤醒等待线程 } finally { lock.unlock(); // 必须释放 } } public void waitForCondition() throws InterruptedException { lock.lockInterruptibly(); // 可中断 try { while (count 10) { condition.await(); // 释放锁并等待 } } finally { lock.unlock(); } } }五、读写锁读读并发读写互斥5.1 ReentrantReadWriteLock 设计public class ReadWriteLockDemo { private final ReentrantReadWriteLock rwl new ReentrantReadWriteLock(); private final Lock readLock rwl.readLock(); private final Lock writeLock rwl.writeLock(); private MapString, Object cache new HashMap(); // 读操作多线程可并发 public Object get(String key) { readLock.lock(); try { return cache.get(key); } finally { readLock.unlock(); } } // 写操作独占 public void put(String key, Object value) { writeLock.lock(); try { cache.put(key, value); } finally { writeLock.unlock(); } } }注意陷阱锁降级写锁降级为读锁可行但锁升级读锁升级为写锁会导致死锁5.2 StampedLock乐观读优化JDK 8public class StampedLockDemo { private final StampedLock sl new StampedLock(); private int x 0, y 0; // 乐观读无锁读取验证后决定是否重试 public int distanceFromOrigin() { long stamp sl.tryOptimisticRead(); int curX x, curY y; if (!sl.validate(stamp)) { // 被修改了升级为悲观读 stamp sl.readLock(); try { curX x; curY y; } finally { sl.unlockRead(stamp); } } return (int) Math.sqrt(curX * curX curY * curY); } }适用场景读多写少且读操作可容忍短暂不一致。六、CAS无锁并发基石6.1 原理// 模拟 CAS 操作实际由硬件指令 cmpxchg 支持 public final boolean compareAndSet(int expect, int update) { // 如果当前值 expect则更新为 update // 否则返回 false return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }三大问题ABA问题→ 使用AtomicStampedReference带版本号自旋开销→ 自适应自旋或退让单变量限制→ 使用AtomicReference封装多个变量6.2 原子类实战// 基本类型 AtomicInteger atomicInt new AtomicInteger(0); atomicInt.incrementAndGet(); // i // 引用类型 AtomicReferenceStudent ref new AtomicReference(); ref.compareAndSet(old, new); // 累加器高吞吐量但弱一致性 LongAdder adder new LongAdder(); adder.increment(); long sum adder.sum(); // 最终一致性七、锁优化最佳实践7.1 锁粒度控制// ❌ 坏锁住整个方法 public synchronized void bad() { // 1. 耗时计算不需要同步 // 2. 少量共享数据操作 } // ✅ 好只锁临界区 public void good() { // 耗时计算 synchronized (this) { // 只同步必要部分 } }7.2 锁分离策略// 使用 ThreadLocal 避免锁 ThreadLocalSimpleDateFormat tl ThreadLocal.withInitial(SimpleDateFormat::new); // 使用 ConcurrentHashMap 分段锁JDK 1.7 // JDK 1.8 改为 CAS synchronized 优化7.3 死锁预防// 破坏循环等待统一锁顺序 public void transfer(Account from, Account to, int amount) { // 按 hash 值排序确保获取锁顺序一致 int fromHash System.identityHashCode(from); int toHash System.identityHashCode(to); if (fromHash toHash) { synchronized (from) { synchronized (to) { /* ... */ } } } else { synchronized (to) { synchronized (from) { /* ... */ } } } }7.4 性能对比建议场景推荐方案竞争不激烈代码简单synchronizedJVM优化需要公平锁/中断/超时ReentrantLock读多写少缓存StampedLock乐观读或ReadWriteLock计数器累加LongAdder高并发 MapConcurrentHashMap单变量原子更新AtomicXXX八、面试高频问题Q1synchronized 和 ReentrantLock 性能谁好JDK 1.6 后两者性能相近。synchronized 在低竞争时有偏向锁优化ReentrantLock 在中高竞争时可中断等待更灵活。Q2AQS 中的 state 有什么作用表示锁状态独占模式下 0空闲0重入次数共享模式下表示剩余资源数。Q3公平锁为什么性能比非公平锁差公平锁需要检查等待队列且上下文切换频繁。非公平锁允许插队减少了线程挂起/唤醒开销。Q4锁的可重入性如何实现在持有锁的线程再次获取时判断当前持有线程是否为自身是则 state1释放时 state-1。九、总结锁选型决策树是否需要可中断/超时 ├─ 是 → ReentrantLock └─ 否 → 竞争是否激烈 ├─ 低 → synchronizedJVM帮你优化 └─ 高 → 读多写少 ├─ 是 → StampedLock乐观读 └─ 否 → ReentrantLock Condition记住锁的最佳实践不是用什么锁而是能不能不用锁。无锁化CAS、ThreadLocal、不可变对象才是终极追求。