synchronized 实现原理
synchronized 是 Java 中最基本的线程同步机制,其实现原理涉及 JVM 底层机制和操作系统交互。以下是其核心实现原理:
- 也有必要看一下 volatile
## 1. 基本实现层次
`synchronized` 的实现分为三个层次,根据竞争情况会逐步升级(锁膨胀):
1. **偏向锁 (Biased Locking)** - 无竞争时的优化
1. **轻量级锁 (Thin Lock)** - 轻度竞争时的优化
1. **重量级锁 (Heavyweight Lock)** - 高竞争时的实现
## 2. 对象头与 Mark Word
每个 Java 对象在内存中的布局包含对象头,其中 **Mark Word** 是实现同步的关键:
```text
|---------------------------------------------------|--------------------|
| Mark Word (64 bits) | State |
|---------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | 0 | Normal(无锁)
|---------------------------------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | 1 | Biased(偏向锁)
|---------------------------------------------------|--------------------|
| ptr_to_lock_record:62 | 00 | Lightweight Locked(轻量级锁)
|---------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | 10 | Heavyweight Locked(重量级锁)
|---------------------------------------------------|--------------------|
| | 11 | Marked for GC |
|---------------------------------------------------|--------------------|
3. 锁升级过程
3.1 偏向锁阶段
-
适用场景:始终只有一个线程访问同步块
-
实现:
- 在 Mark Word 中记录线程 ID
- 后续进入同步块只需简单检查线程 ID
- 无 CAS 操作,性能最高
-
退出:不实际释放锁,只是离开同步块
3.2 轻量级锁阶段
-
触发条件:有第二个线程尝试获取锁(发生竞争)
-
实现:
- JVM 在当前线程栈帧中创建 Lock Record
- 通过 CAS 将 Mark Word 复制到 Lock Record(Displaced Mark Word)
- 尝试用 CAS 将 Mark Word 替换为指向 Lock Record 的指针
-
成功:获得轻量级锁
-
失败:说明存在竞争,升级为重量级锁
3.3 重量级锁阶段
-
实现:
- 指向操作系统级别的互斥量(mutex)
- 未获取锁的线程进入阻塞状态
- 依赖操作系统的线程调度
-
特点:
- 涉及用户态到内核态的切换
- 性能开销大
- 支持 wait/notify 机制
4. 同步代码块实现
对于同步代码块,JVM 使用 monitorenter 和 monitorexit 指令:
public void syncMethod() {
synchronized(this) {
// 代码块
}
}
编译后的字节码:
aload_0 // 加载this引用
dup // 复制引用
astore_1 // 存储引用到局部变量1
monitorenter // 进入监视器(获取锁)
// 同步代码块
aload_1 // 加载局部变量1
monitorexit // 退出监视器(释放锁)
goto 14
// 异常处理路径
aload_1
monitorexit // 确保异常时也能释放锁
athrow
5. 同步方法实现
对于同步方法,JVM 使用 ACC_SYNCHRONIZED 标志:
public synchronized void syncMethod() {
// 方法体
}
- 方法调用时检查 ACC_SYNCHRONIZED 标志
- 自动获取对象锁(实例方法)或类锁(静态方法)
- 方法返回/异常时自动释放锁
6. 性能优化技术
-
锁消除 (Lock Elision) :
- 逃逸分析确定对象不会逃逸时,移除不必要的同步
-
锁粗化 (Lock Coarsening) :
- 将相邻的同步块合并,减少锁获取/释放次数
-
自适应自旋 (Adaptive Spinning) :
- 在轻量级锁竞争时,线程执行忙等待(自旋)
- 自旋次数根据历史成功率动态调整
7. 与 ReentrantLock 对比
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现机制 | JVM 内置实现 | JDK 代码实现 (AQS) |
| 锁获取方式 | 隐式获取/释放 | 显式 lock()/unlock() |
| 灵活性 | 有限 | 支持公平锁、条件变量等 |
| 性能 | Java 6+ 优化后相当 | 竞争激烈时可能更好 |
| 可中断性 | 不支持 | 支持 lockInterruptibly() |
| 超时机制 | 不支持 | 支持 tryLock(timeout) |
现代 JVM 对 synchronized 进行了大量优化,在大多数场景下其性能已与 ReentrantLock 相当,但后者提供了更灵活的锁操作方式。
8. 使用方式:主要有4种:
1:用在代码块上:
public void test() {
//用在代码块上
synchronized (this) {
// do sth
}
}
2:用在对象上:
public void test2() {
// 用在对象上
synchronized (object) {
// do sth
}
}
3:用在方法上:
public synchronized void test3() {
// do sth
}
4:用在静态方法上:(如单例模式)
public static void test4() {
synchronized (Demo_1.class) {
// do sth
}
}
9. synchronized 与 wait()/notify() 的协同工作机制
synchronized 配合 wait() 和 notify()/notifyAll() 实现了 Java 中最基础的线程间协作机制,这种组合构成了 Java 内置的 监视器模式(Monitor Pattern)。
1. 基本关系
-
锁对象的三位一体:
-
每个 Java 对象都关联:
- 一个 锁(用于同步)
- 一个 等待队列(用于
wait()的线程) - 一个 入口队列(竞争锁的线程)
-
-
方法来源:
wait()/notify()是Object类的方法- 必须在
synchronized块内调用(否则抛出IllegalMonitorStateException)
2. 核心方法解析
2.1 wait() 方法
public final void wait() throws InterruptedException;
作用:
- 释放当前持有的锁
- 使当前线程进入 WAITING 状态
- 将线程放入对象的等待队列
特点:
- 调用前必须持有对象锁
- 被唤醒后需要重新竞争锁
- 通常配合条件检查使用(避免虚假唤醒)
标准使用模式:
synchronized(lockObj) {
while(!condition) { // 必须用while循环检查条件
lockObj.wait();
}
// 条件满足后执行操作
}
2.2 notify() 方法
public final void notify();
作用:
- 随机唤醒一个在该对象上等待的线程
- 被唤醒的线程从等待队列移到入口队列
- 不立即释放锁,要等到同步块结束
特点:
- 调用前必须持有对象锁
- 不保证唤醒哪个等待线程
- 被唤醒线程需要重新获取锁才能继续执行
2.3 notifyAll()
public final void notifyAll();
与 notify() 的区别:
- 唤醒所有在该对象上等待的线程
- 所有被唤醒线程竞争锁
- 适用于多条件谓词的情况
3. 工作流程示例
生产者-消费者模型示例:
class Buffer {
private Queue<Integer> queue = new LinkedList<>();
private int capacity;
public Buffer(int capacity) {
this.capacity = capacity;
}
public synchronized void produce(int item) throws InterruptedException {
while(queue.size() == capacity) {
wait(); // 缓冲区满时等待
}
queue.add(item);
notifyAll(); // 唤醒可能等待的消费者
}
public synchronized int consume() throws InterruptedException {
while(queue.isEmpty()) {
wait(); // 缓冲区空时等待
}
int item = queue.remove();
notifyAll(); // 唤醒可能等待的生产者
return item;
}
}
4. 关键实现原理
4.1 等待队列管理
- 每个对象关联一个 _WaitSet 队列
wait()将线程加入此队列notify()从队列中移出线程
4.2 锁释放与重新获取
-
wait()调用时:- 原子性地释放锁
- 将线程状态改为 WAITING
- 进入等待队列
-
被唤醒后:
- 从 WAITING 变为 BLOCKED
- 尝试重新获取锁(可能竞争失败继续等待)
4.3 与 synchronized 的交互
synchronized(obj) { // 1. 获取锁成功
while(condition) {
obj.wait(); // 2. 释放锁并等待
} // 4. 重新获取锁后继续
} // 5. 释放锁
5. 使用注意事项
-
必须持有锁:
- 调用
wait()/notify()前必须获取对象锁 - 否则抛出
IllegalMonitorStateException
- 调用
-
条件检查:
- 必须用
while而不是if检查条件 - 防止虚假唤醒(spurious wakeup)
- 必须用
-
优先使用 notifyAll() :
notify()可能引起"信号劫持"- 除非能确保只唤醒正确类型的线程
-
超时机制:
- 使用
wait(long timeout)避免永久等待 - 结合系统时间检查实现更精确的超时
- 使用
-
中断处理:
wait()可能抛出InterruptedException- 需要合理处理中断
6. 现代替代方案
虽然 synchronized + wait/notify 是基础机制,但现代开发更推荐:
-
java.util.concurrent包:ReentrantLock+Condition- 提供更灵活的等待/通知机制
-
高级同步工具:
CountDownLatch- 表示剩余需要count down的次数(初始值为构造函数指定的计数)
- 递减计数,当state=0时释放所有等待线程
CyclicBarrier- 表示尚未到达屏障的线程数(初始值为构造函数指定的参与线程数)
- 递减计数,当state=0时触发屏障动作并重置
Semaphore- 表示当前可用的许可数(初始值为构造函数指定的许可数)
- 获取许可时递减,释放许可时递增
BlockingQueue