Java中的偏向锁、轻量级锁与重量级锁详解
字数 1708 2025-11-14 16:41:13

Java中的偏向锁、轻量级锁与重量级锁详解

描述
Java中的synchronized关键字在JDK 1.6之后引入了锁升级机制,包括偏向锁、轻量级锁和重量级锁三种状态。这种设计是为了在保证线程安全的同时,减少锁操作带来的性能开销。锁会根据竞争情况从低到高逐步升级,优化多线程环境下的同步性能。

锁升级的背景与目的

  1. 早期synchronized直接使用操作系统互斥锁(重量级锁),性能较差
  2. 统计发现大部分锁在运行时不存在竞争或竞争程度很低
  3. 锁升级机制通过延迟使用重量级锁来优化性能

对象头与锁状态

  1. 每个Java对象在内存中分为三部分:对象头、实例数据、对齐填充
  2. 对象头包含Mark Word(存储对象运行时数据)和类型指针
  3. Mark Word在不同锁状态下会存储不同的内容:
    • 无锁状态:存储对象的hashCode、分代年龄等
    • 偏向锁:存储偏向线程ID、偏向时间戳等
    • 轻量级锁:指向栈中锁记录的指针
    • 重量级锁:指向互斥量(monitor)的指针

偏向锁(Biased Locking)

  1. 设计目标:消除无竞争情况下的同步开销
  2. 工作原理
    • 第一个获取锁的线程会将线程ID写入对象头
    • 之后该线程再次获取锁时,只需检查线程ID是否匹配
    • 如果匹配,直接进入同步代码块,无需原子操作
  3. 偏向锁的获取过程
    • 检查对象头中是否存储了当前线程ID
    • 如果已存储,直接获得锁
    • 如果未存储,通过CAS操作尝试将线程ID写入对象头
  4. 偏向锁的撤销
    • 当其他线程尝试获取锁时,需要撤销偏向锁
    • 暂停拥有偏向锁的线程
    • 检查线程是否存活,如果已结束则直接允许新线程获取锁
    • 如果仍存活,升级为轻量级锁

轻量级锁(Lightweight Locking)

  1. 适用场景:线程交替执行同步块,不存在真正竞争
  2. 加锁过程
    • 在当前线程的栈帧中创建锁记录(Lock Record)
    • 将对象头的Mark Word复制到锁记录中(Displaced Mark Word)
    • 使用CAS操作将对象头替换为指向锁记录的指针
    • 如果成功,获得锁;如果失败,表示存在竞争
  3. 解锁过程
    • 使用CAS操作将Displaced Mark Word替换回对象头
    • 如果成功,解锁完成;如果失败,表示存在竞争,需要膨胀为重量级锁
  4. 自旋优化
    • 在轻量级锁竞争失败后,线程不会立即阻塞
    • 而是进行有限次数的自旋(循环检查锁状态)
    • 如果自旋期间锁被释放,可以避免线程切换的开销

重量级锁(Heavyweight Locking)

  1. 适用场景:存在真正的线程竞争
  2. 实现机制
    • 基于操作系统的互斥量(mutex)实现
    • 涉及线程的阻塞和唤醒,需要从用户态切换到内核态
  3. 监视器(Monitor)结构
    • 每个Java对象都与一个Monitor关联
    • Monitor包含多个重要组件:
      • _owner:指向持有锁的线程
      • _EntryList:等待锁的线程队列
      • _WaitSet:调用wait()方法的线程队列
  4. 重量级锁的工作流程
    • 线程尝试获取锁时,如果锁已被占用,进入EntryList等待
    • 当锁释放时,唤醒EntryList中的线程竞争锁
    • 被唤醒的线程需要重新竞争锁

锁升级的完整流程

  1. 对象初始状态为无锁状态
  2. 当第一个线程访问时,升级为偏向锁
  3. 当第二个线程尝试获取锁时,撤销偏向锁,升级为轻量级锁
  4. 如果轻量级锁竞争激烈(自旋失败),升级为重量级锁
  5. 重量级锁不会降级,除非进行垃圾回收

性能对比与优化建议

  1. 偏向锁:适用于单线程重复加锁的场景
  2. 轻量级锁:适用于线程交替执行的场景
  3. 重量级锁:适用于高并发竞争的场景
  4. 优化建议
    • 如果确定存在竞争,可以通过JVM参数禁用偏向锁(-XX:-UseBiasedLocking)
    • 合理控制同步块的大小,减少锁的持有时间
    • 考虑使用更细粒度的锁或并发容器

实际应用中的注意事项

  1. 锁升级是JVM自动完成的,对开发者透明
  2. 了解锁升级机制有助于编写更高效的并发代码
  3. 在高度竞争的场景下,可以考虑使用ReentrantLock等更灵活的锁机制

这种锁升级机制体现了Java在并发性能优化上的精细设计,通过在适当的时候使用适当的锁级别,实现了性能与功能的平衡。

Java中的偏向锁、轻量级锁与重量级锁详解 描述 Java中的synchronized关键字在JDK 1.6之后引入了锁升级机制,包括偏向锁、轻量级锁和重量级锁三种状态。这种设计是为了在保证线程安全的同时,减少锁操作带来的性能开销。锁会根据竞争情况从低到高逐步升级,优化多线程环境下的同步性能。 锁升级的背景与目的 早期synchronized直接使用操作系统互斥锁(重量级锁),性能较差 统计发现大部分锁在运行时不存在竞争或竞争程度很低 锁升级机制通过延迟使用重量级锁来优化性能 对象头与锁状态 每个Java对象在内存中分为三部分:对象头、实例数据、对齐填充 对象头包含Mark Word(存储对象运行时数据)和类型指针 Mark Word在不同锁状态下会存储不同的内容: 无锁状态:存储对象的hashCode、分代年龄等 偏向锁:存储偏向线程ID、偏向时间戳等 轻量级锁:指向栈中锁记录的指针 重量级锁:指向互斥量(monitor)的指针 偏向锁(Biased Locking) 设计目标 :消除无竞争情况下的同步开销 工作原理 : 第一个获取锁的线程会将线程ID写入对象头 之后该线程再次获取锁时,只需检查线程ID是否匹配 如果匹配,直接进入同步代码块,无需原子操作 偏向锁的获取过程 : 检查对象头中是否存储了当前线程ID 如果已存储,直接获得锁 如果未存储,通过CAS操作尝试将线程ID写入对象头 偏向锁的撤销 : 当其他线程尝试获取锁时,需要撤销偏向锁 暂停拥有偏向锁的线程 检查线程是否存活,如果已结束则直接允许新线程获取锁 如果仍存活,升级为轻量级锁 轻量级锁(Lightweight Locking) 适用场景 :线程交替执行同步块,不存在真正竞争 加锁过程 : 在当前线程的栈帧中创建锁记录(Lock Record) 将对象头的Mark Word复制到锁记录中(Displaced Mark Word) 使用CAS操作将对象头替换为指向锁记录的指针 如果成功,获得锁;如果失败,表示存在竞争 解锁过程 : 使用CAS操作将Displaced Mark Word替换回对象头 如果成功,解锁完成;如果失败,表示存在竞争,需要膨胀为重量级锁 自旋优化 : 在轻量级锁竞争失败后,线程不会立即阻塞 而是进行有限次数的自旋(循环检查锁状态) 如果自旋期间锁被释放,可以避免线程切换的开销 重量级锁(Heavyweight Locking) 适用场景 :存在真正的线程竞争 实现机制 : 基于操作系统的互斥量(mutex)实现 涉及线程的阻塞和唤醒,需要从用户态切换到内核态 监视器(Monitor)结构 : 每个Java对象都与一个Monitor关联 Monitor包含多个重要组件: _ owner:指向持有锁的线程 _ EntryList:等待锁的线程队列 _ WaitSet:调用wait()方法的线程队列 重量级锁的工作流程 : 线程尝试获取锁时,如果锁已被占用,进入EntryList等待 当锁释放时,唤醒EntryList中的线程竞争锁 被唤醒的线程需要重新竞争锁 锁升级的完整流程 对象初始状态为无锁状态 当第一个线程访问时,升级为偏向锁 当第二个线程尝试获取锁时,撤销偏向锁,升级为轻量级锁 如果轻量级锁竞争激烈(自旋失败),升级为重量级锁 重量级锁不会降级,除非进行垃圾回收 性能对比与优化建议 偏向锁 :适用于单线程重复加锁的场景 轻量级锁 :适用于线程交替执行的场景 重量级锁 :适用于高并发竞争的场景 优化建议 : 如果确定存在竞争,可以通过JVM参数禁用偏向锁(-XX:-UseBiasedLocking) 合理控制同步块的大小,减少锁的持有时间 考虑使用更细粒度的锁或并发容器 实际应用中的注意事项 锁升级是JVM自动完成的,对开发者透明 了解锁升级机制有助于编写更高效的并发代码 在高度竞争的场景下,可以考虑使用ReentrantLock等更灵活的锁机制 这种锁升级机制体现了Java在并发性能优化上的精细设计,通过在适当的时候使用适当的锁级别,实现了性能与功能的平衡。