一文搞懂synchronized实现原理

一、synchronized 内部实现原理(深度结合 Monitor)

1. 核心前提:锁的载体是“对象”

synchronized 无论修饰方法还是代码块,最终都要绑定到一个对象上(实例方法绑定 this,静态方法绑定 Class 对象,代码块绑定自定义对象),而对象的 Monitor(监视器/管程) 是实现锁的核心。

2. Monitor 是什么?

Monitor 是 JVM 层面的一种同步工具,本质是一个对象(C++ 实现),包含以下核心结构:

结构 作用
所有者(Owner) 指向当前持有锁的线程,初始为 null
等待队列(WaitSet) 存放调用 wait() 后进入 WAITING 状态的线程
入口队列(EntryList) 存放竞争锁失败、进入 BLOCKED 状态的线程
重入计数器 记录锁的重入次数(支持可重入),初始为 0,获取锁+1,释放锁-1

3. synchronized 结合 Monitor 的执行流程

graph TD A[线程尝试获取锁] --> B{检查Monitor的Owner}; B -->|Owner=null| C[将Owner设为当前线程,重入计数器+1]; B -->|Owner=当前线程| D[重入计数器+1(可重入)]; B -->|Owner=其他线程| E[线程进入EntryList,变为BLOCKED状态]; C --> F[执行临界区代码]; D --> F; F --> G{释放锁}; G --> H[重入计数器-1]; H -->|计数器=0| I[将Owner设为null,唤醒EntryList中一个线程]; H -->|计数器>0| J[继续持有锁,线程执行后续代码]; I --> K[被唤醒的线程重新竞争锁];

关键细节

  • 可重入性:同一线程多次获取同一把锁,仅增加重入计数器,不会死锁(如同步方法调用另一个同步方法);
  • 释放时机:正常执行完临界区代码/抛出异常时,都会释放锁(JVM 保证 monitorexit 一定会执行);
  • 锁升级与 Monitor
    • 偏向锁/轻量级锁阶段:不依赖完整的 Monitor,仅通过对象头的 Mark Word + CAS 实现,减少开销;
    • 升级为重量级锁后:才会关联到完整的 Monitor,此时竞争失败的线程会进入 EntryList 阻塞。

4. synchronized 的底层指令(代码块 vs 方法)

使用形式 底层实现 Monitor 交互方式
同步代码块 monitorenter / monitorexit 指令 执行 monitorenter 获取 Monitor,monitorexit 释放
同步实例方法 ACC_SYNCHRONIZED 标志位 JVM 自动为 this 对象获取/释放 Monitor
同步静态方法 ACC_SYNCHRONIZED 标志位 JVM 自动为 Class 对象获取/释放 Monitor

二、synchronized vs ReentrantLock 核心区别

ReentrantLockjava.util.concurrent.locks 包下的显式锁,与 synchronized(隐式锁)的核心区别如下,用表格对比更清晰:

对比维度 synchronized ReentrantLock
锁的性质 隐式锁(JVM 层面),自动获取/释放 显式锁(API 层面),需手动 lock()/unlock()(建议放 finally)
可重入性 支持(JVM 自动维护重入计数器) 支持(手动维护,默认非公平锁,可指定公平锁)
公平性 非公平锁(无法修改) 支持公平锁/非公平锁(构造方法指定 new ReentrantLock(true)
锁等待中断 不支持(等待的线程无法被中断) 支持(lockInterruptibly() 可中断等待的线程)
超时获取锁 不支持(线程会一直阻塞) 支持(tryLock(long timeout, TimeUnit) 超时放弃)
条件变量(Condition) 仅支持一个(Object 的 wait/notify) 支持多个(newCondition() 创建多个 Condition,精准唤醒线程)
性能 JDK 1.6 后优化(锁升级),与 ReentrantLock 接近 高并发下略优(灵活控制),但需手动释放
异常处理 自动释放锁(即使抛异常) 需在 finally 中释放,否则会死锁
使用复杂度 简单(无需手动管理) 复杂(需手动控制,易出错)

关键区别的代码示例

1. ReentrantLock 基本使用(显式锁)
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {
    private static final ReentrantLock lock = new ReentrantLock(true); // 公平锁

    public static void doTask() {
        lock.lock(); // 手动获取锁
        try {
            System.out.println("线程 " + Thread.currentThread().getName() + " 执行任务");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock(); // 必须在finally中释放,避免死锁
        }
    }

    public static void main(String[] args) {
        new Thread(ReentrantLockDemo::doTask, "线程1").start();
        new Thread(ReentrantLockDemo::doTask, "线程2").start();
    }
}
2. ReentrantLock 中断等待 + 超时获取
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockAdvance {
    private static final ReentrantLock lock = new ReentrantLock();

    public static void tryLockWithTimeout() {
        try {
            // 超时获取锁:3秒内获取不到则放弃
            if (lock.tryLock(3, TimeUnit.SECONDS)) {
                try {
                    System.out.println(Thread.currentThread().getName() + " 获取锁成功");
                    Thread.sleep(5000); // 模拟长时间执行业务
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println(Thread.currentThread().getName() + " 超时未获取锁");
            }
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + " 等待锁时被中断");
            Thread.currentThread().interrupt(); // 恢复中断标记
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(ReentrantLockAdvance::tryLockWithTimeout, "线程A");
        Thread t2 = new Thread(ReentrantLockAdvance::tryLockWithTimeout, "线程B");
        
        t1.start();
        Thread.sleep(1000);
        t2.start();
        t2.interrupt(); // 中断线程B的等待
    }
}

输出结果

线程A 获取锁成功
线程B 等待锁时被中断
线程A 释放锁

三、使用场景选择建议

  1. 优先用 synchronized:大部分场景下(简单同步、低并发),synchronized 足够用,且 JVM 自动优化,无需手动管理,出错概率低;
  2. 选 ReentrantLock 的场景
    • 需要公平锁;
    • 需要中断等待锁的线程、超时获取锁;
    • 需要多个条件变量(精准唤醒部分线程);
    • 高并发场景下需要更灵活的锁控制。

总结

  1. synchronized 基于 JVM 层面的 Monitor 实现,通过对象头的锁状态+锁升级机制保证性能,是隐式锁,自动获取/释放;
  2. ReentrantLock 是 API 层面的显式锁,需手动 lock()/unlock(),支持公平锁、中断等待、超时获取等高级特性;
  3. 选型原则:简单场景用 synchronized,复杂并发场景(需高级特性)用 ReentrantLock,且使用 ReentrantLock 时必须在 finally 中释放锁。
posted @ 2026-03-05 20:15  七星6609  阅读(2)  评论(0)    收藏  举报