Java 锁机制深度解析系列一总览与核心概念前言在并发编程中锁是协调多线程对共享资源访问的核心手段。Java 提供了丰富的锁机制从最底层的synchronized到 JUCjava.util.concurrent包中的各类显式锁形成了一个层次分明的锁体系。一、锁的分类体系从不同维度来看Java 中的锁可以划分为多个类别按粒度维度粗粒度锁细粒度锁锁范围大整个对象/类小分段/行并发度低高实现复杂度简单复杂典型代表synchronized(类.class)ConcurrentHashMap 分段锁适用场景低频访问、简单同步高频并发、高性能需求按性质维度隐式锁synchronized显式锁Lock管理方式JVM 自动加锁/解锁开发者手动 lock() / unlock()灵活性低固定语法高支持超时、中断、尝试锁类型内置互斥锁多种实现互斥、读写、乐观读性能经过持续优化偏向锁等取决于具体实现适用场景简单同步需求复杂并发控制按竞争策略公平锁 vs 非公平锁维度公平锁非公平锁获取策略FIFO 先来后到允许插队饥饿无饥饿 ✅可能导致饥饿 ❌吞吐量较低更高 ✅典型实现ReentrantLock(true)synchronized、ReentrantLock(false)可重入锁 vs 不可重入锁维度可重入锁不可重入锁同一线程重复获取允许 ✅不允许 ❌死锁使用复杂度简单复杂需自行避免重入典型实现ReentrantLock、synchronized自定义简单锁实际应用几乎全部使用很少使用按状态JVM 内部这是理解synchronized性能的关键锁状态标志位适用场景Mark Word 存储无锁01无竞争hashCode、分代年龄偏向锁01单一线程反复获取线程 ID轻量级锁00少量竞争CAS 自旋Lock Record 指针重量级锁10多线程激烈竞争Monitor 指针二、核心术语速览术语说明示例CASCompare-And-Swap无锁原子操作AtomicInteger.incrementAndGet()自旋线程不挂起循环尝试获取锁轻量级锁的获取方式锁消除JIT 编译时发现锁对象不会逃逸移除锁StringBuffer在方法内使用时锁粗化JIT 将相邻的加锁/解锁合并循环内反复加锁的场景管程Monitor操作系统中管理线程同步的机制Java 中每个对象关联一个 Monitorsynchronized的底层实现AQSAbstractQueuedSynchronizerJUC 锁的基石ReentrantLock、CountDownLatch 等三、如何选择锁实际开发中的选择原则场景推荐方案原因方法级简单同步synchronized代码简洁JVM 自动优化需要超时/中断ReentrantLock提供tryLock(timeout)、lockInterruptibly()读多写少ReadWriteLock或StampedLock读读不互斥大幅提升并发度极致读性能StampedLock乐观读乐观读完全不加锁仅事后校验单个变量更新Atomic*类CAS 无锁性能优于锁集合类并发访问ConcurrentHashMap、CopyOnWriteArrayList内部已实现高效分段锁或写时复制四、性能对比概览以下为 JDK 8 典型场景下的相对性能数值越大越好仅供参考说明JDK 6 对synchronized做了大量优化偏向锁、轻量级锁、锁消除、锁粗化现代 JDK 中synchronized在低竞争场景下性能已不逊于ReentrantLock。五、一个直观的例子从无锁到有锁// 场景10000 个线程对共享变量自增// 方案1无锁线程不安全staticintcount10;// 结果不可预测// 方案2synchronizedstaticintcount20;staticsynchronizedvoidincrement2(){count2;}// 方案3ReentrantLockstaticintcount30;staticLocklocknewReentrantLock();staticvoidincrement3(){lock.lock();try{count3;}finally{lock.unlock();}}// 方案4AtomicInteger无锁 CASstaticAtomicIntegercount4newAtomicInteger(0);// count4.incrementAndGet();性能表现粗略取决于线程数和竞争程度低竞争 CAS ≈ synchronized ≈ ReentrantLock 高竞争 synchronized重量级 ReentrantLock CAS但CAS可能导致自旋开销 读多写少 ReadWriteLock ReentrantLock synchronized六、底层基石对象头与 Mark Word理解 Java 锁必须理解对象的内存布局。一个 Java 对象在堆内存中包含------------------ ← 对象起始 | Mark Word | 8 字节32位JVM/ 8 字节64位JVM 压缩后 ------------------ | Klass Pointer | 4/8 字节指向类元数据 ------------------ | 实例数据 | (按需) ------------------ | 对齐填充 | ------------------Mark Word是锁状态的核心存储区它的结构随锁状态变化64位JVM Mark Word 布局 无锁 (Normal): | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:0 | 01 | 偏向锁 (Biased): | thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | 01 | 轻量级锁 (Lightweight Locked): | ptr_to_lock_record:62 | 00 | 重量级锁 (Heavyweight Locked): | ptr_to_monitor:62 | 10 | GC 标记 (Marked for GC): | 11 |关键观察Mark Word 在无锁时存储对象的 identity hashcode 和 GC 分代年龄一旦加锁这些信息会被覆盖并暂存到其他地方。这就是为什么重写了hashCode()的对象无法使用偏向锁——因为偏向锁需要利用 Mark Word 中原本存储 hashcode 的位置来存储线程 ID。七、锁升级路径synchronized 的核心synchronized在 JDK 6 之后采用了锁升级机制这是其性能大幅提升的根本原因无锁 → 偏向锁 → 轻量级锁 → 重量级锁 只升不降但GC后可能恢复到无锁轻量级锁失败 无锁 ──[单线程]──→ 偏向锁 ──[多线程交替]──→ 轻量级锁 ──[多线程竞争]──→ 重量级锁 ↓ ↓ 撤销偏向锁 CAS自旋失败 (需在安全点) (线程挂起/阻塞)