在 Java 并发编程中,synchronized
是最基础也最常用的线程安全保障手段。它看似简单,实则背后藏着 JVM 的复杂优化逻辑,且在不同 JDK 版本中经历了多次性能升级。今天我们就从 “实现原理”“版本差异”“工程作用” 三个维度,彻底吃透synchronized
。
一、synchronized 的底层实现:不止是 “锁”
synchronized
的核心是通过 “互斥” 保证临界区代码的原子执行,但其实现依赖 JVM 的对象头与监视器锁(Monitor) 机制,不同修饰对象的实现逻辑略有差异。
1. 锁的载体:对象头里的 “锁状态”
Java 中所有对象都有一个 “对象头”(Object Header),其中Mark Word
字段存储了对象的锁状态信息,这是synchronized
实现的关键。
默认情况下,Mark Word
存储对象哈希码、GC 年龄等信息;当对象被加锁时,Mark Word
会更新为 “锁指针”,指向对应的锁结构(如偏向锁记录、轻量级锁栈帧)。
2. 两种锁模式的实现逻辑
synchronized
根据修饰对象不同,锁的载体也不同:
修饰普通方法 / 代码块:锁载体是 “对象实例”(普通方法锁
this
,代码块锁自定义对象);修饰静态方法:锁载体是 “类对象”(如
XXX.class
),因为静态方法属于类而非实例。
无论哪种模式,最终都会关联到监视器锁(Monitor) ——JVM 中的一种同步机制,每个对象 / 类都对应一个 Monitor。当线程进入synchronized
代码块时,会尝试 “获取 Monitor”;执行完后 “释放 Monitor”,其他线程需等待 Monitor 释放才能竞争。
二、JDK 版本差异:从 “重量级锁” 到 “锁升级” 的性能跃迁
synchronized
的性能在 JDK 1.6 前后有天壤之别,核心原因是 JDK 1.6 引入了 “锁升级” 机制,通过偏向锁→轻量级锁→重量级锁的渐进式升级,平衡 “线程安全” 与 “性能开销”。
| JDK 版本 | 核心优化 | 锁机制特点 | 适用场景 |
| ----------- | -------------- | -------------------------------------- | -------------- |
| JDK 1.5 及之前 | 无 | 仅支持 “重量级锁”,依赖操作系统内核态互斥量(Mutex),线程切换成本高 | 极少并发场景(性能差) |
| JDK 1.6 及之后 | 锁升级、CAS 算法、自旋锁 | 支持三种锁状态,按需升级,避免频繁内核态切换 | 全场景覆盖(低并发→高并发) |
锁升级的完整流程(JDK 1.6+):
偏向锁:当只有一个线程反复进入临界区时,JVM 会在
Mark Word
中记录该线程 ID,后续该线程进入时无需竞争,直接获取锁(几乎无开销);轻量级锁:当有第二个线程竞争锁时,偏向锁升级为轻量级锁。线程会通过CAS(Compare and Swap) 尝试修改
Mark Word
的锁指针,若成功则获取锁,失败则自旋(循环重试);重量级锁:当自旋次数超过阈值(默认 10 次),或更多线程参与竞争时,轻量级锁升级为重量级锁。此时线程会阻塞并进入内核态等待,由操作系统调度唤醒(开销最高,但适合高并发)。
这种 “能不加锁就不加锁,能轻锁就不轻转重” 的设计,让synchronized
在低并发场景下性能接近无锁,高并发场景下保证安全。
三、工程开发中的核心作用:解决并发 “痛点”
在实际项目中,synchronized
是应对线程安全问题的 “基础工具”,主要解决以下三类核心场景:
1. 保证原子操作:避免 “中间态” 数据错乱
当多个线程执行 “多步操作”(如 “判断库存→扣减库存”“读取计数→累加计数”)时,synchronized
能保证这些操作成为 “不可分割的原子块”,避免其他线程在中间步骤打断。
工程实例:电商秒杀中的库存扣减,用synchronized
包裹 “判断库存是否大于 0→扣减库存” 的逻辑,防止超卖。
2. 保证可见性:避免 “缓存不一致”
线程修改共享变量后,若未及时刷新到主内存,其他线程可能读取到旧值(缓存不一致)。synchronized
在释放锁时会强制刷新缓存,让修改后的变量同步到主内存;获取锁时会清空本地缓存,从主内存重新读取变量,从而保证可见性。
工程实例:多线程控制的 “服务开关”,用synchronized
修饰开关的修改与读取逻辑,确保线程能立即感知开关状态变化。
3. 保证有序性:避免 “指令重排序”
JVM 为优化性能会重排序指令,可能导致代码执行顺序与预期不一致(如单例模式中 “对象未初始化完成就赋值给引用”)。synchronized
会禁止临界区内的指令重排序,同时保证 “一个线程释放锁后,另一个线程获取锁前” 的操作不会交叉,从而保证有序性。
工程实例:非 volatile 修饰的单例模式,用synchronized
修饰实例创建逻辑,避免指令重排序导致的空指针异常。
四、工程使用的 “避坑” 建议
缩小锁粒度:尽量修饰代码块而非整个方法,只锁定 “真正需要同步的逻辑”(如库存扣减只锁扣减部分,而非整个商品查询方法),减少线程阻塞时间;
避免锁对象变化:锁对象(如
this
、自定义对象)不能是 “可变对象”(如用 String 作为锁对象,可能因字符串常量池复用导致锁冲突),建议用private final Object lock = new Object();
定义不可变锁对象;不嵌套锁:避免在
synchronized
代码块中嵌套另一个synchronized
(如 “锁 A” 中调用 “锁 B” 的同步方法),防止死锁;高并发场景慎用:若并发量极高(如每秒万级请求),
synchronized
的重量级锁可能成为性能瓶颈,此时可结合 “并发容器(如 ConcurrentHashMap)”“原子类(如 AtomicInteger)” 等更轻量的方案优化。
五、总结
synchronized
从 JDK 1.5 的 “重量级锁” 进化到 JDK 1.6 + 的 “智能锁升级”,早已不是 “性能杀手”,而是工程开发中 “简单可靠” 的并发解决方案。它的核心价值在于用较低的理解成本,解决原子性、可见性、有序性三大并发痛点,是 Java 开发者必须掌握的基础工具。
在实际项目中,无需盲目追求复杂的并发框架,先学好synchronized
的底层逻辑与使用场景,才能在 “安全” 与 “性能” 之间找到平衡,写出更稳定的并发代码。