synchronized(三)

一、synchronized的底层实现基础

在JVM中,synchronized的实现依赖于"对象头"和"监视器锁(Monitor)"。要理解synchronized的底层逻辑,首先需要明确两个核心概念:

  1. 对象头(Object Header):Java中每个对象都有一个对象头,用于存储对象的元数据信息(如哈希码、GC分代年龄、锁状态等)。synchronized的锁状态就存储在对象头的"Mark Word"部分(Mark Word是对象头的核心组成部分,占4字节或8字节,取决于JVM位数)。 Mark Word的结构会随锁状态变化而变化,核心状态包括:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。

  2. 监视器锁(Monitor):Monitor是JVM内部的一个同步工具,也称为"管程"。每个对象在JVM中都对应一个Monitor对象,当线程尝试获取synchronized锁时,本质上是在获取该对象对应的Monitor的所有权。 Monitor的核心结构包括:

  • 持有线程(Owner):当前持有Monitor的线程,初始为null;

  • 等待队列(WaitSet):调用wait()方法后释放锁的线程会进入该队列;

  • 阻塞队列(EntryList):未获取到锁的线程会进入该队列,等待Owner释放锁后竞争。

二、synchronized的锁升级过程(JDK 6+优化)

在JDK 6之前,synchronized是一种"重量级锁",每次获取和释放锁都需要调用操作系统的内核态指令,性能开销较大。JDK 6对synchronized进行了优化,引入了"偏向锁"和"轻量级锁",实现了"锁升级"的机制------锁会根据线程竞争的激烈程度,从无锁状态逐步升级为偏向锁、轻量级锁、重量级锁,避免了不必要的性能开销。

  1. 1. 无锁状态:对象刚创建时,Mark Word处于无锁状态,存储对象的哈希码、GC分代年龄等信息,此时无线程竞争。

  2. 2. 偏向锁(单线程竞争):当只有一个线程多次获取同一个锁时,JVM会将锁升级为偏向锁。核心逻辑是:线程第一次获取锁时,会在Mark Word中记录当前线程的ID(偏向线程ID),之后该线程再次获取锁时,无需进行CAS操作或内核态调用,直接通过判断Mark Word中的偏向线程ID是否为当前线程即可,几乎无性能开销。 触发偏向锁撤销的场景:当有其他线程尝试获取该锁时,偏向锁会被撤销,升级为轻量级锁。撤销过程需要暂停持有偏向锁的线程,开销较小。

  3. 3. 轻量级锁(少量线程竞争,无阻塞):当有两个或多个线程交替获取锁(竞争不激烈,无线程阻塞)时,偏向锁撤销后升级为轻量级锁。核心逻辑是:

  • 线程获取锁时,会在自己的工作内存中创建一个"锁记录(Lock Record)",存储锁对象的Mark Word副本;

  • 通过CAS操作将锁对象的Mark Word更新为指向当前线程的Lock Record的指针;

  • 若CAS操作成功,线程获取轻量级锁;若失败(说明有其他线程竞争),则自旋重试(通过循环尝试CAS,避免阻塞)。 触发轻量级锁升级的场景:当自旋重试次数达到阈值(默认10次),或有线程在自旋过程中被阻塞时,轻量级锁会升级为重量级锁。

  1. 4. 重量级锁(激烈竞争,线程阻塞):当多个线程同时竞争锁,且竞争激烈(出现线程阻塞)时,轻量级锁升级为重量级锁。此时,锁的实现依赖于Monitor,线程获取锁失败后会进入Monitor的阻塞队列(EntryList),并放弃CPU执行权(阻塞状态),等待持有锁的线程释放锁后被唤醒竞争。 重量级锁的特点:性能开销大(涉及内核态调用),但能保证线程安全,适用于激烈竞争的场景。

三、synchronized的原子性实现原理

synchronized保证原子性的核心是"Monitor的独占性":当线程获取到Monitor(即获取锁)后,会独占该锁,其他线程无法获取,直到持有锁的线程执行完同步代码块并释放锁。具体过程如下:

  1. 线程执行同步代码前,必须先获取锁(通过CAS操作或Monitor竞争);

  2. 获取锁成功后,线程进入同步代码块执行逻辑,此时其他线程尝试获取锁会被阻塞(轻量级锁自旋,重量级锁进入阻塞队列);

  3. 线程执行完同步代码后,释放锁(更新Mark Word状态,唤醒阻塞队列中的线程)。

通过这种"独占式"的执行机制,synchronized确保了同步代码块中的所有操作会作为一个原子操作执行,不会被其他线程打断。

四、synchronized与volatile的区别

synchronized和volatile都是Java中解决并发问题的关键字,但两者的作用和实现原理差异较大,核心区别如下:

对比维度 synchronized volatile
核心作用 保证原子性、可见性、有序性 仅保证可见性、有序性,不保证原子性
实现原理 基于对象头和Monitor,锁升级机制 基于内存屏障(Memory Barrier),禁止指令重排序
适用场景 多线程修改共享资源、复合操作 多线程读取共享资源、避免指令重排序(如单例模式)
性能开销 有锁升级优化,轻竞争下开销小,激烈竞争下开销大 开销极小(仅内存屏障),无锁机制

五、底层指令层面的实现

当我们编译synchronized修饰的代码时,JVM会在字节码层面插入两个核心指令:monitorentermonitorexit,分别对应锁的获取和释放。

  1. monitorenter:线程执行到monitorenter指令时,会尝试获取当前对象对应的Monitor所有权。若Monitor的Owner为null,则当前线程成为Owner;若当前线程已为Owner,则计数器加1(可重入锁的实现基础);若Owner为其他线程,则当前线程进入阻塞队列。

  2. monitorexit:线程执行到monitorexit指令时,会将Monitor的计数器减1。当计数器减为0时,释放Monitor(Owner设为null),并唤醒阻塞队列中的线程竞争锁。

需要注意的是,JVM会确保每个monitorenter指令都有对应的monitorexit指令(即使代码抛出异常,也会通过异常处理机制插入monitorexit),保证锁一定会被释放,避免死锁。

相关推荐
许泽宇的技术分享2 小时前
AgentFramework:错误处理策略
开发语言·c#
阿里嘎多学长2 小时前
2025-12-21 GitHub 热点项目精选
开发语言·程序员·github·代码托管
wanghowie2 小时前
01.04 Java基础篇|泛型、注解与反射实战
java·开发语言·windows
DechinPhy2 小时前
使用Python免费合并PDF文件
开发语言·数据库·python·mysql·pdf
inputA2 小时前
【rt-thread】点灯实验和按键输入实验
c语言·笔记·学习·实时操作系统
qq_252614412 小时前
python爬虫爬取视频
开发语言·爬虫·python
Radan小哥2 小时前
Docker学习笔记—day013
笔记·学习·docker
言之。2 小时前
Claude Code Skills 实用使用手册
java·开发语言
Rinai_R2 小时前
关于 Go 的内存管理这档事
java·开发语言·golang