synchronized 原理

简介: `synchronized` 是 Java 中实现线程同步的关键字,通过对象头中的 Monitor 和锁机制确保同一时间只有一个线程执行同步代码。其底层依赖 Mark Word 和 Monitor 控制锁状态,支持偏向锁、轻量级锁和重量级锁的升级过程,以优化性能。同步方法和同步块在实现方式上有所不同,前者通过 `ACC_SYNCHRONIZED` 标志隐式加锁,后者通过 `monitorenter` 和 `monitorexit` 指令显式控制。此外,`synchronized` 还保证内存可见性和 Happens-Before 关系,使共享变量在多线程间正确同步。

synchronized 是 Java 中用于实现线程同步的关键字,确保同一时刻只有一个线程可以执行被修饰的代码块或方法。其核心原理基于 Java 对象头中的监视器(Monitor)锁机制,下面从底层实现、锁升级、内存语义三个层面详细解析:

1. 底层实现:Monitor 与对象头

Monitor(监视器)

  • 本质:每个 Java 对象都可以关联一个 Monitor,它是实现同步的基础,底层由 C++ 的 ObjectMonitor 类实现。
  • 作用:Monitor 是一个同步工具,相当于一个许可证,线程只有获取到 Monitor 才能执行同步代码,执行完毕后释放 Monitor。

对象头(Object Header)

  • Java 对象在内存中的布局分为三部分:对象头、实例数据、对齐填充。
  • Mark Word:对象头的一部分,用于存储对象的哈希码、分代年龄和锁状态信息。锁状态不同时,Mark Word 的存储内容会发生变化:
    • 无锁:存储对象的哈希码和分代年龄。
    • 轻量级锁:存储指向线程栈中锁记录的指针。
    • 重量级锁:存储指向 Monitor 的指针。

2. 锁的升级过程

JDK 6 之后,synchronized 进行了大量优化,引入了偏向锁、轻量级锁、重量级锁的升级机制,以减少线程竞争带来的性能损耗。锁的升级过程是不可逆的:

偏向锁(Biased Locking)

  • 适用场景:只有一个线程访问同步块的情况。
  • 原理
    1. 当第一个线程访问同步块并获取锁时,会在 Mark Word 中记录该线程的 ID(偏向线程 ID)。
    2. 后续该线程再次进入同步块时,无需任何同步操作,直接获取锁,效率极高。
  • 撤销条件:当有其他线程尝试竞争偏向锁时,持有偏向锁的线程会被挂起,偏向锁升级为轻量级锁。

轻量级锁(Lightweight Lock)

  • 适用场景:多个线程交替访问同步块,无实际竞争。
  • 原理
    1. 线程进入同步块前,JVM 会在当前线程的栈帧中创建一个锁记录(Lock Record)。
    2. 通过 CAS(Compare and Swap)操作尝试将 Mark Word 更新为指向锁记录的指针:
      • 成功:获取轻量级锁。
      • 失败:表示有其他线程竞争,锁升级为重量级锁。
  • 释放锁:通过 CAS 将 Mark Word 恢复为无锁状态。

重量级锁(Heavyweight Lock)

  • 适用场景:多个线程同时竞争锁。
  • 原理
    1. 锁升级为重量级锁后,Mark Word 存储指向 Monitor 的指针。
    2. 未获取到锁的线程会被阻塞(进入 Monitor 的 EntryList),释放 CPU 资源。
    3. 锁释放时,会唤醒 EntryList 中的线程重新竞争。
  • 缺点:线程阻塞和唤醒涉及用户态与内核态的切换,性能开销大。

3. 同步方法与同步块的实现差异

同步方法(synchronized method

  • 隐式实现:通过方法修饰符 synchronized 实现。
  • 原理:JVM 通过方法表中的 ACC_SYNCHRONIZED 标志来识别同步方法。调用时,JVM 会自动检查该标志,获取当前对象或类的 Monitor 后执行方法。

同步块(synchronized(this)

  • 显式实现:通过 synchronized 代码块实现。
  • 原理
    1. 进入同步块前,执行 monitorenter 指令,尝试获取 Monitor。
    2. 退出同步块时,执行 monitorexit 指令(正常退出和异常退出各有一个),释放 Monitor。

4. 内存语义与 Happens-Before 规则

synchronized 不仅保证了线程互斥,还具有内存可见性的语义:

  • 解锁(释放 Monitor):JVM 会将当前线程的本地内存中的共享变量刷新到主内存。
  • 加锁(获取 Monitor):JVM 会将主内存中的共享变量最新值更新到当前线程的本地内存。
  • Happens-Before 规则:对一个 Monitor 的解锁操作 Happens-Before 后续对同一个 Monitor 的加锁操作,确保了可见性。

5. 性能优化建议

  • 减少锁的粒度:避免对整个方法加锁,优先使用同步块。
  • 降低锁的持有时间:将不需要同步的代码移出同步块。
  • 优先使用偏向锁/轻量级锁场景:对于单线程或交替执行的同步代码,性能提升显著。
  • 考虑替代方案:高并发场景下,可使用 ReentrantLockConcurrentHashMap 等更灵活的同步工具。

总结

synchronized 的核心原理是基于 Monitor 和对象头的锁机制,通过锁升级策略(偏向锁 → 轻量级锁 → 重量级锁)在不同场景下平衡性能和线程安全。理解其底层实现有助于写出更高效、更安全的多线程代码。

目录
相关文章
|
存储 Java
ArrayList的初始化容量与扩容机制解析
ArrayList的初始化容量与扩容机制解析
|
存储 监控 安全
吃透synchronized实现原理
吃透synchronized实现原理
269 0
|
存储 关系型数据库 MySQL
MVCC多版本并发控制
MVCC多版本并发控制 1、MVCC MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
760 0
|
5月前
|
缓存 Java
对比 synchronized 和 volatile
`synchronized` 和 `volatile` 是 Java 并发编程中的两个关键机制,各有侧重。`synchronized` 用于实现线程的互斥访问,保证原子性、可见性和有序性,适用于需要锁的场景;而 `volatile` 更轻量,仅确保变量的可见性和有序性,适用于状态标志等无需复合操作的场景。两者可互补使用,如双重检查单例中结合二者优势。合理选择有助于提升并发性能与代码安全性。
249 0
|
6月前
|
存储 Java Linux
详细地说一说零拷贝
我是小假 期待与你的下一次相遇 ~
367 1
详细地说一说零拷贝
|
7月前
|
消息中间件 架构师 Java
美团面试:对比分析 RocketMQ、Kafka、RabbitMQ 三大MQ常见问题?
美团面试:对比分析 RocketMQ、Kafka、RabbitMQ 三大MQ常见问题?
美团面试:对比分析 RocketMQ、Kafka、RabbitMQ 三大MQ常见问题?
|
消息中间件 RocketMQ
如何保证RocketMQ消息有序?
如何保证RocketMQ消息有序?
|
缓存 安全 Java
全面解读ConcurrentHashMap:Java中的高效并发数据结构
全面解读ConcurrentHashMap:Java中的高效并发数据结构
2523 2
|
存储 安全 Java
synchronized原理详解(通俗易懂超级好)
当系统检查到锁是重量级锁之后,会把等待想要获得锁的线程进行阻塞,被阻塞的线程不会消耗cpu。但是阻塞或者唤醒一个线程时,都需要操作系统来帮忙,这就需要从用户态转换到内核态,而转换状态是需要消耗很多时间的,有可能比用户执行代码的时间还要长。
synchronized原理详解(通俗易懂超级好)
|
负载均衡 监控 Java
SpringCloud常见面试题(一):SpringCloud 5大组件,服务注册和发现,nacos与eureka区别,服务雪崩、服务熔断、服务降级,微服务监控
SpringCloud常见面试题(一):SpringCloud 5大组件,服务注册和发现,nacos与eureka区别,服务雪崩、服务熔断、服务降级,微服务监控
26579 8
SpringCloud常见面试题(一):SpringCloud 5大组件,服务注册和发现,nacos与eureka区别,服务雪崩、服务熔断、服务降级,微服务监控

热门文章

最新文章