Java中synchronized的原理
一、核心机制
synchronized 是Java实现线程同步的关键字,通过对象锁和锁升级策略确保多线程环境下的数据安全与性能优化。
二、锁的实现基础
-
对象头与Mark Word
- 每个Java对象在内存中分为三部分:对象头、实例数据、对齐填充。
- 对象头中的Mark Word存储锁状态、哈希码、GC年龄等信息。
- 锁状态通过Mark Word的特定标志位标识,支持锁升级。
-
监视器锁(Monitor)
-
每个对象关联一个Monitor(由C++实现的
ObjectMonitor结构)。 -
Monitor包含:
- EntryList:竞争锁的线程队列。
- WaitSet:调用
wait()的线程队列。 - Owner:持有锁的线程。
- 计数器:记录锁的重入次数。
-
三、锁的四种状态与升级过程
| 锁状态 | 特点 | 适用场景 |
|---|---|---|
| 无锁 | 对象未被锁定,Mark Word存储哈希码等普通信息。 | 无竞争环境 |
| 偏向锁 | 记录线程ID,同一线程多次获取锁无需CAS操作。 | 单线程重复访问 |
| 轻量级锁 | 通过CAS自旋尝试获取锁,失败后短暂自旋(避免线程挂起)。 | 低并发,线程交替执行 |
| 重量级锁 | 竞争激烈时,线程阻塞并交由操作系统调度(涉及用户态到内核态切换)。 | 高并发,长时间锁竞争 |
升级流程:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
(根据线程竞争动态调整,不可降级)
四、synchronized的使用方式
-
修饰实例方法
public synchronized void method() { /* 锁对象为当前实例 */ } -
修饰静态方法
public static synchronized void method() { /* 锁对象为类的Class对象 */ } -
同步代码块
synchronized (lockObject) { /* 显式指定锁对象 */ }
五、锁的重入性
-
同一线程可多次获取同一把锁(通过计数器实现)。
public synchronized void methodA() { methodB(); // 可重入 } public synchronized void methodB() { /* ... */ }
六、性能优化与对比
-
偏向锁
- 优点:无竞争时减少CAS开销。
- 触发:首次获取锁时设置线程ID。
- 撤销:检测到竞争时升级为轻量级锁。
-
轻量级锁
- 优点:通过自旋减少线程阻塞。
- 自旋条件:竞争线程少且持有锁时间短。
- 失败处理:自旋失败后升级为重量级锁。
-
重量级锁
- 缺点:线程阻塞与唤醒开销大(涉及内核态切换)。
- 适用场景:高并发且锁持有时间长。
七、与ReentrantLock对比
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 锁获取方式 | 自动获取与释放(代码块结束) | 需手动lock()和unlock() |
| 可中断 | ❌ 不支持 | ✅ 支持lockInterruptibly() |
| 公平锁 | ❌ 仅非公平锁 | ✅ 可设置为公平锁 |
| 条件变量 | 单一wait()/notify() | 支持多个Condition |
八、总结
-
synchronized 通过对象头锁标志位和Monitor机制实现线程同步。
-
锁升级策略(偏向锁→轻量级锁→重量级锁)平衡性能与安全性。
-
适用场景:
- 简单同步需求:优先使用
synchronized(语法简洁,自动管理锁)。 - 复杂场景(如超时、公平锁):考虑
ReentrantLock。
- 简单同步需求:优先使用
口诀:
「对象头里藏玄机,锁的状态分四级
偏向轻量重量级,竞争升级按场景
可重入锁计数器,同步代码保安全!」