synchronized关键字的底层实现
synchronized关键字的底层实现从JVM到字节码硬件全维度解析synchronized是Java原生的重量级锁JDK1.6后做了大量优化成为偏向锁→轻量级锁→重量级锁的自适应锁其底层实现依托JVM的字节码指令和操作系统的同步原语同时结合对象头的锁状态标记实现锁的升级与降级是Java并发中保证原子性、可见性、有序性的核心关键字。本文从字节码层面、对象头层面、锁状态升级层面、重量级锁底层四层拆解其实现原理兼顾基础概念与底层细节覆盖面试高频考点。一、先明确核心定位synchronized是可重入的互斥锁支持修饰实例方法、静态方法、代码块不同修饰方式对应不同的锁对象此前已讲此处快速回顾为底层实现铺垫修饰非静态方法锁对象为当前实例对象this修饰静态方法锁对象为当前类的Class对象修饰代码块锁对象为括号内的自定义对象如synchronized(obj){}锁的是obj。底层核心逻辑JVM通过对锁对象的对象头进行状态标记结合字节码指令实现锁的获取与释放同时根据竞争程度自动实现锁的升级偏向→轻量→重量减少锁竞争的性能损耗。二、字节码层面synchronized的基础实现synchronized的最外层实现体现在Java字节码指令中JVM为其提供了**monitorenter和monitorexit两个核心指令分别对应锁的获取和锁的释放**。1. 修饰代码块的字节码实现以简单的同步代码块为例看其编译后的字节码publicclassSyncDemo{privateObjectobjnewObject();publicvoidtest(){synchronized(obj){// 同步代码块System.out.println(synchronized code);}}}编译后对obj加锁的核心字节码指令// 先获取obj对象的引用压入操作数栈 aload_1 // 进入同步监视器获取obj的锁对应monitorenter monitorenter // 执行同步代码块逻辑打印语句 ... // 退出同步监视器释放锁对应monitorexit monitorexit // 异常场景的monitorexit保证锁一定释放 monitorexit核心细节monitorenter执行该指令时当前线程会尝试获取锁对象对应的监视器Monitor的所有权若监视器的进入数为0线程成功获取将进入数设为1若当前线程已持有该监视器进入数1可重入的实现若其他线程持有监视器当前线程阻塞直到监视器被释放。monitorexit执行该指令时线程将监视器的进入数-1当进入数减为0时释放监视器阻塞的线程可重新竞争锁。两个monitorexitJVM会在正常执行路径和异常执行路径各插入一个monitorexit保证无论是否发生异常锁都会被释放避免死锁。2. 修饰方法的字节码实现synchronized修饰方法时不会生成monitorenter/monitorexit指令而是通过方法的访问标志实现JVM为方法提供了ACC_SYNCHRONIZED访问标志当方法被该标志标记时表明此方法为同步方法。核心逻辑线程调用该方法时会先尝试获取方法对应的锁对象的监视器获取成功则执行方法体执行完毕后释放监视器获取失败则线程阻塞直到监视器被释放。字节码标识示例// 同步非静态方法ACC_PUBLIC ACC_SYNCHRONIZED public synchronized void test1(); flags: ACC_PUBLIC, ACC_SYNCHRONIZED // 同步静态方法ACC_PUBLIC ACC_STATIC ACC_SYNCHRONIZED public static synchronized void test2(); flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED3. 核心共性监视器Monitor无论是同步代码块还是同步方法底层都依赖监视器Monitor其本质是操作系统的互斥量Mutex是一个ObjectMonitor对象C实现核心属性包括_owner持有当前监视器的线程_EntryList阻塞等待获取监视器的线程队列_WaitSet调用wait()后等待的线程队列_count监视器的进入数可重入计数。Monitor的核心作用作为锁的底层载体实现线程的互斥与阻塞。三、对象头层面锁状态的存储载体synchronized的锁状态并非存储在锁对象本身而是存储在锁对象的对象头中Java中所有对象的对象头是实现synchronized的核心数据结构。1. Java对象的内存布局Java对象在堆内存中的布局分为3部分对象头是核心对象 对象头Header 实例数据Instance Data 对齐填充Padding2. 对象头的结构对象头的结构随JVM位数32/64位略有差异64位JVM开启指针压缩为生产环境主流其结构如下部分大小核心作用Mark Word64bit存储锁状态、线程ID、哈希值等核心信息锁的核心载体Klass Pointer32bit指向对象所属类的Class对象指针压缩后数组长度32bit仅数组对象有记录数组长度核心重点Mark Word标记字段是实现synchronized锁状态的关键它是一个动态变化的字段会根据锁的竞争程度改变存储内容实现锁状态的切换。3. Mark Word的动态存储结构64位JVMMark Word采用内存复用技术不同锁状态下存储不同的信息默认无锁状态的存储结构为无锁状态哈希值31bit 分代年龄4bit 是否是偏向锁1bit0 锁状态2bit01当对象被作为synchronized的锁对象时Mark Word会根据锁状态更新不同锁状态对应不同的Mark Word存储内容核心锁状态的Mark Word结构锁状态锁标志位2bit偏向锁标志1bitMark Word存储内容无锁010哈希值 分代年龄 0 01偏向锁011持有锁的线程ID Epoch 分代年龄 101轻量级锁00-指向线程栈中锁记录的指针 00重量级锁10-指向堆中ObjectMonitor对象的指针 10GC标记11-空仅用于GC标记核心逻辑JVM通过修改Mark Word的锁标志位和存储内容标记当前对象的锁状态同时记录持有锁的线程/监视器信息实现锁的管理。四、锁状态升级从偏向锁到重量级锁JDK1.6核心优化JDK1.6之前synchronized是纯重量级锁直接依赖操作系统的Mutex线程阻塞/唤醒会触发内核态与用户态的切换性能极低JDK1.6后JVM为synchronized做了锁膨胀优化引入偏向锁、轻量级锁只有当锁竞争激烈时才会升级为重量级锁实现自适应的锁策略。核心原则锁升级是单向的偏向锁 → 轻量级锁 → 重量级锁不能降级保证锁的性能最优锁的选择基于竞争程度无竞争→偏向锁轻度竞争→轻量级锁重度竞争→重量级锁所有锁都是可重入的基于Mark Word/Monitor的计数实现。1. 偏向锁无竞争场景的最优解适用场景一个线程多次获取同一把锁无任何其他线程竞争是单线程执行同步代码的最优锁状态。核心设计锁偏向于第一个获取它的线程该线程后续再次获取锁时无需任何竞争操作直接获取避免锁竞争的开销。实现原理锁的获取线程第一次获取锁对象时JVM将锁对象的Mark Word中偏向锁标志设为1锁标志位设为01同时将当前线程的ID存入Mark Word该线程后续再次获取锁时只需判断Mark Word中的线程ID是否为当前线程ID✅ 是直接获取锁无需任何额外操作❌ 否触发偏向锁的撤销或升级。锁的释放偏向锁无需主动释放只有当有其他线程竞争锁时才会触发偏向锁的撤销。偏向锁的撤销当其他线程尝试获取偏向锁时JVM会先暂停持有锁的线程检查持有锁的线程是否还在执行✅ 是将偏向锁升级为轻量级锁继续执行❌ 否将Mark Word恢复为无锁状态重新偏向新线程。核心优势无需任何CAS操作轻量级锁的核心单线程下性能接近无锁是JDK1.6后synchronized的默认锁状态JDK1.8默认开启可通过-XX:-UseBiasedLocking关闭。2. 轻量级锁轻度竞争场景的CAS实现适用场景多个线程交替获取同一把锁无同时竞争即轻度竞争场景是偏向锁无法满足后的次优解。核心设计基于CASCompare And Swap比较并交换实现锁的获取与释放无需进入内核态避免线程阻塞的开销。实现原理前提轻量级锁的获取前会先将锁对象的Mark Word复制到当前线程的栈帧中创建一个锁记录Lock Record对象存储该副本。锁的获取CAS线程通过CAS操作将锁对象的Mark Word替换为指向当前线程栈帧中Lock Record的指针✅ CAS成功将锁标志位设为00轻量级锁获取成功❌ CAS失败判断当前锁对象的Mark Word是否已指向当前线程的Lock Record可重入✅ 是直接获取锁❌ 否说明有其他线程同时竞争锁轻量级锁升级为重量级锁。锁的释放CAS线程通过CAS操作将栈帧中Lock Record的Mark Word副本写回锁对象的Mark Word✅ CAS成功锁释放Mark Word恢复为无锁状态❌ CAS失败说明锁已升级为重量级锁进入重量级锁的释放流程。核心优势基于用户态的CAS实现无需内核态切换性能远高于重量级锁适用于线程交替执行的轻度竞争场景。3. 重量级锁重度竞争场景的操作系统实现适用场景多个线程同时竞争同一把锁重度竞争是synchronized的最终锁状态。核心设计依赖操作系统的互斥量Mutex和ObjectMonitor对象实现线程竞争失败会进入阻塞状态触发内核态与用户态的切换。实现原理锁的获取当轻量级锁CAS失败时JVM会将锁对象的Mark Word替换为指向堆中ObjectMonitor对象的指针锁标志位设为10升级为重量级锁竞争锁的线程会进入ObjectMonitor的**_EntryList队列并阻塞**操作系统的park()方法放弃CPU执行权当持有锁的线程释放锁后会唤醒_EntryList中的一个线程重新竞争锁。锁的释放持有锁的线程执行完同步代码后会调用unpark()方法唤醒_EntryList中的一个线程将ObjectMonitor的_owner设为null同时将锁对象的Mark Word恢复为无锁/偏向锁状态被唤醒的线程重新竞争锁若竞争失败则再次阻塞。核心缺点线程阻塞/唤醒会触发内核态与用户态的切换该操作开销极大因此重量级锁的性能最低但能保证多线程同时竞争时的互斥性。4. 锁状态升级完整流程总结无锁状态 → 第一个线程获取锁 → 偏向锁线程ID存入Mark Word ↓有其他线程竞争偏向锁撤销 轻量级锁CAS替换Mark Word为Lock Record指针 ↓多个线程同时竞争CAS失败 重量级锁Mark Word指向ObjectMonitor线程阻塞五、重量级锁的底层操作系统的同步原语synchronized的重量级锁最终依托操作系统的底层同步原语实现核心涉及互斥量Mutex、线程的阻塞与唤醒。1. 内核态与用户态Java程序运行在用户态而线程的阻塞、唤醒、调度属于操作系统内核的功能需要切换到内核态内核态与用户态的切换是重量级锁性能低的核心原因因为该操作需要保存线程的上下文开销极大。2. 核心系统调用park()线程阻塞的核心系统调用由操作系统实现让线程放弃CPU执行权进入阻塞状态unpark()线程唤醒的核心系统调用唤醒被park()阻塞的线程使其重新参与CPU调度Mutex操作系统的互斥量实现线程的互斥访问是ObjectMonitor的底层实现。六、synchronized的三大特性实现底层支撑synchronized能保证原子性、可见性、有序性其底层由JVM的监视器机制和内存模型JMM支撑原子性通过监视器的互斥性实现同一时刻只有一个线程能持有监视器保证同步代码块的执行不可分割可见性通过JMM的刷新策略实现线程释放锁时会将工作内存中的修改刷新到主内存线程获取锁时会将主内存中的最新数据加载到工作内存有序性通过JMM的指令重排规则实现synchronized修饰的代码块会被JVM视为一个同步屏障屏障前后的指令禁止重排同时保证代码块内的指令按顺序执行。七、核心面试考点总结synchronized的字节码实现同步代码块用monitorenter/monitorexit同步方法用ACC_SYNCHRONIZED访问标志底层均依赖Monitor锁对象的核心载体锁状态存储在对象头的Mark Word中通过修改Mark Word的锁标志位和存储内容实现锁状态切换锁升级的过程偏向锁无竞争→轻量级锁CAS轻度竞争→重量级锁ObjectMonitor重度竞争单向升级基于竞争程度自适应偏向锁的核心单线程下无需CAS直接通过线程ID判断性能最优轻量级锁的核心基于用户态CAS实现无需内核态切换重量级锁的核心依托操作系统的Mutex和ObjectMonitor线程阻塞会触发内核态/用户态切换性能最低与Lock的底层差异synchronized是JVM原生实现底层C自动加锁释放Lock是Java代码实现AQS手动加锁释放底层同样依赖CAS和LockSupportpark/unpark。八、整体总结synchronized的底层实现是JVM操作系统的协同结果其核心演进是从纯重量级锁到自适应锁表层通过字节码指令monitorenter/monitorexit/ACC_SYNCHRONIZED实现锁的获取与释放中层通过对象头的Mark Word存储锁状态实现锁的升级与管理底层偏向锁/轻量级锁基于JVM的CAS实现用户态重量级锁基于操作系统的Mutex和ObjectMonitor实现内核态。JDK1.6后的一系列优化偏向锁、轻量级锁、锁粗化、锁消除让synchronized的性能与ReentrantLock接近成为Java并发中简单、安全、高效的互斥锁选择。