synchronized 原理

简介: 本文详解 Java 中 `synchronized` 的底层实现原理及锁升级机制。通过 Monitor 对象管理线程竞争,涉及 owner、EntryList、WaitSet 等结构,并介绍偏向锁、轻量级锁、重量级锁的升级过程。同时对比 `synchronized` 与 `volatile`、`Lock` 的区别,涵盖原子性、可见性、有序性及功能扩展性,帮助理解并发编程中的线程安全机制。

synchronized 底层原理

以重量级锁为例,比如 T0、T1 两个线程同时执行加锁代码,已经出现了竞争(代码如下)

synchronized(obj) { // 加锁
    ...
} // 解锁
  1. 当执行到行1 的代码时,会根据 obj 的对象头找到创建此对象对应的 Monitor 对象(C++对象)
  • Monitor的核心结构包含:
  • owner:指向持有该Monitor的线程
  • WaitSet:存放出于wait状态的线程队列
  • EntryList:存放出于阻塞状态的线程队列
  • recursions:记录线程重入次数
  1. 检查 Monitor 对象的 owner 属性,用 Cas 操作去设置 owner 为当前线程,Cas 是原子操作,只能有一个线程能成功
  1. 假设 T0 Cas 成功,那么 T0 就加锁成功,将 recursions 置为1,若线程已持有该Monitor(可重入),则        recursions +1,然后T0线程继续执行 synchronized 代码块内的部分
  2. T1 这边 Cas 失败,会自旋若干次,重新尝试加锁,如果
  1. 重试过程中 T0 释放了锁,则 T1 不必阻塞,加锁成功
  2. 重试时 T0 仍持有锁,则 T1 会进入 Monitor 的等待队列EntryList,将来 T0 解锁后会唤醒它恢复运行(去重新抢锁)


synchronized 锁升级


synchronized 锁有三个级别:偏向锁、轻量级锁、重量级锁,性能从左到右逐渐降低

  • 如果就一个线程对同一对象加锁,此时就用偏向锁
  • 又来一个线程,与前一个线程交替为对象加锁,但只是交替,没有竞争,此时要升级为轻量级锁
  • 如果多个线程加锁时发生了竞争,必须升级为重量级锁

【说明】

  • 自 java 6 开始对 synchronized 提供了锁升级功能,之前只有重量级锁
  • 但从 java 15 开始,偏向锁被标记为已废弃,将来会移除(因为实际带来的性能提升不明显,某些情况下反而影响性能)


对比 synchronized 和 volatile


并发编程需要从三个方面考虑线程安全,分别是:原子性、可见性、有序性

  • volatile 修饰共享变量,可以保证它的可见性和有序性,但不能保证原子性(JMM模型)
  • synchronized 代码块,不仅能保证共享变量的可见性、有序性,同时也能保证原子性


对比 synchronized 和 Lock


  • synchronized 是关键字,Lock 是 Java 接口
  • 前者底层是 C++ 代码实现锁,后者是 Java 自己的代码来实现锁
  • Lock 功能更多,比如可以选择是公平锁还是非公平锁、可以设置加锁超时时间、可打断等
  • Lock 的提供多种扩展实现(例如读写锁),可以根据场景选择更合适的实现
  • Lock 释放锁需要调用 unlock 方法,而 synchronzied 在代码块结束无需显式调用就可以释放锁
相关文章
|
2月前
|
存储 安全 Java
synchronized 原理
`synchronized` 是 Java 中实现线程同步的关键字,通过对象头中的 Monitor 和锁机制确保同一时间只有一个线程执行同步代码。其底层依赖 Mark Word 和 Monitor 控制锁状态,支持偏向锁、轻量级锁和重量级锁的升级过程,以优化性能。同步方法和同步块在实现方式上有所不同,前者通过 `ACC_SYNCHRONIZED` 标志隐式加锁,后者通过 `monitorenter` 和 `monitorexit` 指令显式控制。此外,`synchronized` 还保证内存可见性和 Happens-Before 关系,使共享变量在多线程间正确同步。
365 0
|
24天前
|
安全 Java 数据库
解释悲观锁与乐观锁
悲观锁如synchronized和Lock,通过阻塞确保线程安全;乐观锁如AtomicInteger,采用重试机制应对竞争。前者适合高竞争场景,后者适用于低竞争环境。二者思想类似,广泛应用于Java及数据库领域。
|
24天前
|
存储 数据库连接
ThreadLocal 的原理
ThreadLocal 用于实现多线程环境下变量隔离,每个线程拥有独立资源,避免共享导致的竞争问题。其原理是通过线程内部的 ThreadLocalMap 存储资源,以 ThreadLocal 为 key,资源为 value。使用时需注意调用 remove() 清理资源,防止内存泄漏。
|
26天前
|
消息中间件 Java 测试技术
RocketMQ-5.3.1异常、原因汇总表
本简介汇总了常见的RocketMQ异常信息及其解决方案,涵盖主题配置、网络通信、SSL设置、权限控制、消息发送与消费等多个方面,帮助开发者快速定位和理解异常原因。
163 22
|
消息中间件 存储 canal
3分钟白话RocketMQ系列—— 如何保证消息不丢失
3分钟白话RocketMQ系列—— 如何保证消息不丢失
4637 1
|
24天前
|
安全 网络协议 Linux
深入理解Linux内核模块:加载机制、参数传递与实战开发
本文深入解析了Linux内核模块的加载机制、参数传递方式及实战开发技巧。内容涵盖模块基础概念、加载与卸载流程、生命周期管理、参数配置方法,并通过“Hello World”模块和字符设备驱动实例,带领读者逐步掌握模块开发技能。同时,介绍了调试手段、常见问题排查、开发规范及高级特性,如内核线程、模块间通信与性能优化策略。适合希望深入理解Linux内核机制、提升系统编程能力的技术人员阅读与实践。
132 1
|
2月前
|
消息中间件 缓存 监控
MQ消息积压 / Rocketmq 积压 最全的处理方案。 (秒懂+图解+史上最全)
MQ消息积压 / Rocketmq 积压 最全的处理方案。 (秒懂+图解+史上最全)
MQ消息积压 / Rocketmq 积压 最全的处理方案。 (秒懂+图解+史上最全)
|
24天前
|
Java Linux API
网络编程中,BIO、NIO、AIO的区别
本文介绍了Web开发中客户端与服务器交互的流程,以及BIO、NIO和AIO三种I/O模型的区别与应用场景。重点分析了线程阻塞与非阻塞对并发性能的影响,并探讨了Java 21中虚拟线程对传统BIO模型的优化。