当前位置: 首页 > news >正文

JAVA中synchronized重量级锁加锁和释放锁的机制

一、重量级锁的核心:Monitor 对象

当锁升级为重量级锁时,对象的 Mark Word 会指向一个 ‌Monitor 对象‌(由 C++ 的 ObjectMonitor 实现)。该对象管理锁的竞争和线程调度。

ObjectMonitor 关键字段
class ObjectMonitor {
    void*     _header;       // 原始对象的 Mark Word
    void*     _owner;        // 持有锁的线程
    intptr_t  _recursions;   // 锁重入次数(可重入锁的关键)
    WaitSet   _WaitSet;      // 调用 wait() 的线程队列(等待条件变量)
    EntryList _EntryList;    // 竞争锁失败的线程队列(阻塞状态)
    // ...
};

二、加锁机制(Locking)

当线程尝试获取重量级锁时,流程如下:

1. 快速路径(Fast Path)
  • 锁未被占用‌(_owner == NULL):
    线程通过 ‌CAS 操作‌ 将 _owner 设置为当前线程,并更新 _recursions=1,直接获取锁。
2. 锁已被占用
  • 锁被当前线程重复获取‌(可重入性):
    _recursions 递增 1,无需竞争。
  • 锁被其他线程占用‌:
    线程进入 _EntryList 队列,并调用操作系统 ‌pthread_mutex_lock‌ 进入阻塞状态(通过内核态调度),直到被唤醒。
3. 内核态阻塞
  • 系统调用‌:通过操作系统的 ‌互斥量(Mutex)‌ 和 ‌条件变量(Condition Variable)‌ 实现线程阻塞。
  • 性能代价‌:用户态到内核态的切换、上下文切换、线程调度延迟。

三、释放锁机制(Unlocking)

释放锁时,需处理锁状态和线程唤醒:

1. 重入锁释放
  • 减少重入计数‌:_recursions--,若 _recursions > 0,直接返回(锁未完全释放)。
2. 完全释放锁
  • 清除 _owner‌:将 _owner 设置为 NULL
  • 唤醒阻塞线程‌:
    • 策略‌:默认采用 ‌非公平策略‌,直接从 _EntryList 或 _WaitSet 中唤醒一个线程。
    • 操作‌:调用操作系统 ‌pthread_mutex_unlock‌ 释放互斥量,并通过 ‌pthread_cond_signal‌ 通知等待线程。
3. 线程唤醒
  • 从 EntryList 获取线程‌:被唤醒的线程重新竞争锁。
  • 锁传递‌:若采用 ‌公平锁模式‌(通过 synchronized 无法配置,需用 ReentrantLock),按队列顺序唤醒线程。

四、流程图解

加锁流程
线程尝试获取锁 → 
    if (锁未被占用):
        CAS 设置 _owner 为当前线程 → 成功获取锁
    else if (锁属于当前线程):
        _recursions++ → 重入成功
    else:
        进入 _EntryList → 阻塞(内核态)
释放流程
线程释放锁 →
    _recursions-- → 
    if (_recursions == 0):
        设置 _owner = NULL →
        唤醒 _EntryList/_WaitSet 中的线程(内核态)

五、性能影响与优化

1. 性能问题
  • 上下文切换‌:线程阻塞和唤醒涉及内核态操作,耗时约 ‌微秒级‌。
  • 竞争激烈场景‌:频繁的重量级锁操作会导致吞吐量骤降。
2. 优化建议
  • 减少同步块粒度‌:缩小 synchronized 代码块范围。
  • 替换并发工具‌:使用 ReentrantLock(可配置公平性)、StampedLock 或 CAS 操作。
  • 避免锁竞争‌:通过无锁数据结构(如 ConcurrentHashMap)或线程本地存储(ThreadLocal)。

六、示例代码与底层验证

1. 代码示例(触发重量级锁)
public class HeavyLockDemo {
    private static final Object lock = new Object();
    private static int counter = 0;

    public static void main(String[] args) {
        // 多线程竞争锁
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                synchronized (lock) {
                    counter++; // 竞争激烈时,锁升级为重量级锁
                }
            }).start();
        }
    }
}
2. 查看 Monitor 状态
  • JVM 参数‌:
    -XX:+PrintAssembly          # 查看汇编代码(需 hsdis 库)
    -XX:+PrintSafepointStatistics # 输出安全点信息
    

  • 工具‌:使用 ‌jstack‌ 或 ‌Java Mission Control‌ 观察线程的阻塞状态(BLOCKED)。

总结

  • 重量级锁的核心‌:通过 Monitor 对象和操作系统互斥量管理线程阻塞与唤醒。
  • 可重入性‌:通过 _recursions 字段实现锁的重复获取。
  • 设计权衡‌:在保证线程安全的前提下,牺牲部分性能以处理高竞争场景。
  • 优化方向‌:减少锁竞争、缩小同步范围、替换更高效的并发工具。

相关文章:

  • Golang中间件的原理与实现
  • Linux 配置NFS服务器
  • Edge浏览器快速开启IE模式
  • MySQL 锁机制全面解析:乐观锁与悲观锁实现及深度剖析
  • ubuntu 2204键盘按键映射修改
  • DataGear 5.3.0 制作支持导出表格数据的数据可视化看板
  • OceanBase的闪回查询功能实践
  • IP数据报报文格式
  • 英伟达「虚拟轨道+AI调度」专利:开启自动驾驶3.0时代的隐形革命
  • 离散的数据及参数适合用什么算法做模型
  • vscode_拼写关闭
  • 从 WPF 到 MAUI:跨平台 UI 开发的进化之路
  • C++使用do {} while(false)的好处
  • 机器学习模型类型
  • OpenCV 图形API(2)为什么需要图形API?
  • 关于《数据资源建设费用测算标准》(工作组讨论稿)意见征集的通知
  • 白盒测试用例的设计
  • 【学Rust写CAD】16 0、1、-1代数单位元(algebraic_units.rs)
  • Redis:概念与常用命令
  • mysql-分区和性能
  • 两国战机均未侵入对方领空,巴方公布对印回击细节
  • 上海飞银川客机触地复飞后备降西安,亲历者:不少乘客都吐了
  • 安顺市原副市长、市公安局原局长顾长华任贵州省民委副主任
  • 以色列计划“占领加沙”,特朗普下周中东行结束之际将是“机会窗口”
  • 上海车展侧记|中国汽车产业的韧性从何而来
  • 射箭世界杯赛上海站即将开幕,多方联动讲好上海故事