【Java并发编程】锁机制:synchronized:底层实现、对象头、锁升级流程(偏向锁→轻量级锁→重量级锁)、锁优化、可重入性(附《思维导图》+《面试高频考点清单》)

简介: 本文系统梳理Java中`synchronized`锁机制:涵盖原子性、可见性、有序性三大特性;详解三种使用方式及对应锁对象;深入字节码(monitorenter/exit)、Monitor实现、对象头Mark Word状态变迁;完整解析偏向锁→轻量级锁→重量级锁的不可逆升级流程;并总结JVM锁优化(自适应自旋、锁消除、锁粗化)与常见误区。内容兼具深度与面试实用性。

思维导图

Java并发编程:synchronized锁机制 系统性知识体系

一、synchronized基础概述

1.1 核心作用

  • 原子性:保证临界区代码的原子执行,防止多线程交错执行导致的数据不一致
  • 可见性:根据JMM规范,锁释放时会将工作内存数据刷新到主内存,锁获取时会清空工作内存并从主内存重新加载
  • 有序性:禁止临界区内部代码与外部代码的指令重排序(临界区内部仍允许重排序)

1.2 三种使用方式

使用方式 锁对象 作用范围
修饰实例方法 this(当前实例对象) 同一实例的所有同步方法互斥
修饰静态方法 类的Class对象 该类所有实例的静态同步方法互斥
修饰代码块 括号内指定的对象 同一锁对象的所有同步代码块互斥

二、synchronized底层实现原理

2.1 字节码层面实现

synchronized在字节码层面通过两个指令实现:

  • monitorenter:进入同步块,尝试获取对象监视器(monitor)的所有权
  • monitorexit:退出同步块,释放对象监视器的所有权

关键细节

  • 编译器会自动在同步代码块前后插入这两个指令
  • 为了保证异常时也能释放锁,编译器会生成一个异常表,在异常发生时自动执行monitorexit
  • 每个对象都关联一个monitor,当monitor被占用时就处于锁定状态

2.2 对象监视器(Monitor)原理

Monitor是操作系统提供的同步原语,在HotSpot中由ObjectMonitor实现,核心结构:

ObjectMonitor {
   
  _owner        // 持有当前锁的线程指针
  _count        // 锁计数器(支持可重入性)
  _waiters      // 等待队列(调用wait()的线程)
  _entryList    // 入口队列(竞争锁失败的线程)
  _recursions   // 重入次数
}

执行流程

  1. 线程执行monitorenter时,检查_count是否为0
  2. 若为0,将_owner设为当前线程,_count加1
  3. 若不为0且_owner是当前线程,_count加1(可重入)
  4. 若不为0且_owner不是当前线程,线程进入_entryList阻塞
  5. 执行monitorexit时,_count减1,减到0时释放锁并唤醒_entryList中的线程

三、Java对象头详解(锁实现的基础)

在32位HotSpot虚拟机中,对象在内存中分为三部分:对象头实例数据对齐填充。其中对象头是synchronized锁机制的核心载体。

3.1 对象头结构(32位)

长度 内容 说明
32bit Mark Word 存储对象的哈希码、GC分代年龄、锁状态标志等
32bit Klass Pointer 指向对象所属类的元数据的指针
可选 数组长度 只有数组对象才有,占32bit

3.2 Mark Word的状态变化

Mark Word是一个动态的数据结构,会根据对象的锁状态复用存储空间,不同状态下的内容如下:

锁状态 25bit 4bit 1bit 2bit
无锁 对象哈希码 GC分代年龄 0 01
偏向锁 线程ID GC分代年龄 1 01
轻量级锁 指向栈中锁记录的指针 - - 00
重量级锁 指向monitor对象的指针 - - 10
GC标记 - - 11

64位Mark Word说明

  • 线程ID占54bit,GC分代年龄占4bit,偏向锁标志1bit,锁标志2bit
  • 开启指针压缩时,Klass Pointer占32bit,否则占64bit

四、完整锁升级流程(JDK 1.6+)

JDK 1.6对synchronized进行了重大优化,引入了偏向锁轻量级锁,使得锁不再直接升级为重量级锁,而是按照偏向锁→轻量级锁→重量级锁的顺序逐步升级,且升级后不可降级。

4.1 偏向锁(Biased Locking)

设计思想:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得。因此可以让锁偏向于第一个获取它的线程,后续该线程获取锁时无需任何同步操作。

获取流程

  1. 检查对象头Mark Word中的锁标志位是否为01,偏向锁标志是否为1
  2. 如果是可偏向状态,检查Mark Word中的线程ID是否为当前线程ID
  3. 如果是,直接进入同步块,无需任何CAS操作
  4. 如果不是,使用CAS操作将Mark Word中的线程ID替换为当前线程ID
  5. 如果CAS成功,获取偏向锁,进入同步块
  6. 如果CAS失败,说明存在竞争,触发偏向锁撤销

偏向锁撤销

  • 偏向锁的撤销需要等待全局安全点(STW)
  • 暂停持有偏向锁的线程,检查该线程是否还在执行同步块
  • 如果线程已经退出同步块,将对象头恢复为无锁状态,然后重新偏向
  • 如果线程仍在执行同步块,升级为轻量级锁

注意:偏向锁默认是开启的,但在应用启动后几秒钟才会激活。可以通过-XX:-UseBiasedLocking关闭偏向锁。

4.2 轻量级锁(Lightweight Locking)

设计思想:当存在多个线程交替执行同步块时,使用用户态的CAS操作代替内核态的互斥量,避免线程切换的开销。

获取流程

  1. 线程在自己的栈帧中创建一个名为锁记录(Lock Record)的空间
  2. 将对象头的Mark Word复制到锁记录中(称为Displaced Mark Word)
  3. 使用CAS操作尝试将对象头的Mark Word替换为指向当前线程锁记录的指针
  4. 如果CAS成功,获取轻量级锁,进入同步块
  5. 如果CAS失败,检查对象头的Mark Word是否指向当前线程的锁记录
  6. 如果是,说明是重入,直接进入同步块
  7. 如果不是,说明存在竞争,自旋等待一定次数
  8. 如果自旋后仍未获取到锁,升级为重量级锁

释放流程

  1. 使用CAS操作将锁记录中的Displaced Mark Word替换回对象头
  2. 如果CAS成功,释放锁完成
  3. 如果CAS失败,说明锁已经升级为重量级锁,进入重量级锁的释放流程

4.3 重量级锁(Heavyweight Locking)

设计思想:当存在大量线程同时竞争锁时,轻量级锁的自旋会消耗大量CPU资源,此时升级为依赖操作系统互斥量的重量级锁。

执行流程

  1. 锁升级为重量级锁后,对象头的Mark Word指向操作系统的monitor对象
  2. 竞争失败的线程会被操作系统挂起(进入阻塞状态)
  3. 当锁被释放时,操作系统会唤醒所有等待的线程,重新竞争锁
  4. 竞争失败的线程再次被挂起,如此循环

性能特点

  • 优点:不会消耗CPU资源进行自旋
  • 缺点:线程阻塞和唤醒需要操作系统内核态和用户态的切换,开销很大

4.4 锁升级完整流程图

无锁状态
   ↓
偏向锁(单线程访问)
   ↓ 发生竞争
轻量级锁(多线程交替访问)
   ↓ 竞争加剧/自旋失败
重量级锁(多线程同时竞争)

五、JVM对synchronized的锁优化技术

5.1 自旋锁与自适应自旋

自旋锁

  • 线程竞争锁失败时,不立即阻塞,而是循环执行几次空操作,等待锁释放
  • 避免了线程切换的开销,但会消耗CPU资源
  • 适用于锁持有时间很短的场景

自适应自旋(JDK 1.6引入):

  • 自旋次数不再固定,而是由前一次在同一个锁上的自旋时间和锁持有者的状态决定
  • 如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机会认为这次自旋也很可能成功,从而允许自旋更长的时间
  • 如果对于某个锁,自旋很少成功过,那么虚拟机会直接跳过自旋过程,阻塞线程

5.2 锁消除

定义:JIT编译器在运行时,对一些代码上要求同步但实际上不可能存在共享数据竞争的锁进行消除。

判断依据:基于逃逸分析技术,如果一个对象不会逃逸出当前线程,那么这个对象的锁操作可以被安全地消除。

示例

public String concatString(String s1, String s2, String s3) {
   
    // StringBuffer是线程安全的,append方法是synchronized的
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    sb.append(s3);
    return sb.toString();
}
  • 上述代码中,sb对象是局部变量,不会逃逸出方法,因此JIT编译器会消除所有append方法的锁操作

5.3 锁粗化

定义:将多个连续的加锁和解锁操作合并成一个更大范围的加锁和解锁操作,减少频繁的锁操作带来的开销。

示例

// 优化前
for (int i=0; i<100; i++) {
   
    synchronized (lock) {
   
        // 操作
    }
}

// 优化后
synchronized (lock) {
   
    for (int i=0; i<100; i++) {
   
        // 操作
    }
}

注意:锁粗化也有边界,如果循环内有耗时操作,粗化会导致锁持有时间过长,反而降低并发性。

六、synchronized的可重入性

6.1 可重入性定义

可重入性是指同一个线程已经获取了某个锁之后,再次请求获取该锁时不会被阻塞。

6.2 可重入性实现原理

  • 每个锁对象都有一个锁计数器和一个持有线程指针
  • 当线程第一次获取锁时,计数器加1,持有线程指针指向该线程
  • 当同一个线程再次获取该锁时,计数器再次加1
  • 当线程释放锁时,计数器减1
  • 当计数器减到0时,锁被完全释放,其他线程可以获取

6.3 可重入性的意义

  • 避免了死锁:如果没有可重入性,子类重写父类的同步方法并调用父类方法时会发生死锁
  • 简化了编程模型:程序员不需要担心自己已经获取的锁会导致自己阻塞

示例

public class ReentrantExample {
   
    public synchronized void methodA() {
   
        System.out.println("执行methodA");
        methodB(); // 同一个线程再次获取锁,不会阻塞
    }

    public synchronized void methodB() {
   
        System.out.println("执行methodB");
    }
}

七、synchronized与ReentrantLock的对比

特性 synchronized ReentrantLock
实现方式 JVM层面实现 Java API层面实现
可重入性 支持 支持
公平性 非公平锁 支持公平锁和非公平锁
可中断性 不支持 支持lockInterruptibly()
超时获取 不支持 支持tryLock(long timeout)
条件变量 不支持(wait/notify) 支持多个Condition对象
自动释放 是(异常时自动释放) 否(必须手动在finally中释放)
性能 JDK 1.6+优化后与ReentrantLock相当 略高(在高竞争场景下)

八、核心考点与常见误区

8.1 核心考点

  1. 对象头Mark Word在不同锁状态下的结构
  2. 完整的锁升级流程及每个阶段的触发条件
  3. 偏向锁、轻量级锁、重量级锁的优缺点及适用场景
  4. 可重入性的实现原理
  5. JVM的各种锁优化技术

8.2 常见误区

  1. ❌ 错误:synchronized是重量级锁,性能很差
    ✅ 正确:JDK 1.6+引入了偏向锁和轻量级锁,在无竞争或低竞争场景下性能很高
  2. ❌ 错误:锁可以降级
    ✅ 正确:锁只能升级,不能降级。偏向锁可以撤销,但不是降级
  3. ❌ 错误:synchronized修饰静态方法和实例方法效果一样
    ✅ 正确:静态方法锁的是Class对象,实例方法锁的是this对象,两者互不影响
  4. ❌ 错误:自旋锁一定比阻塞锁好
    ✅ 正确:自旋锁适用于锁持有时间很短的场景,如果锁持有时间很长,自旋会浪费大量CPU资源

Java synchronized锁机制 面试版核心考点清单(可直接背诵)

一、基础必背考点

  1. synchronized三大特性:保证原子性、可见性、有序性(禁止临界区内外重排序)
  2. 三种使用方式及锁对象
    • 实例方法:锁this(当前实例)
    • 静态方法:锁类名.class(Class对象)
    • 代码块:锁括号内指定的任意对象
  3. 字节码实现:通过monitorentermonitorexit指令,异常时自动释放锁
  4. 可重入性实现:每个锁对象有持有线程指针锁计数器,同一线程重入时计数器递增,释放时递减,减到0才真正释放

二、对象头核心考点(32位HotSpot)

  1. 对象头组成:Mark Word(32bit)+ Klass Pointer(32bit)+ 数组长度(仅数组对象)
  2. Mark Word状态复用表(必考)
锁状态 锁标志位 核心存储内容
无锁 01 对象哈希码+GC分代年龄
偏向锁 01(偏向位=1) 线程ID+GC分代年龄
轻量级锁 00 指向栈中锁记录的指针
重量级锁 10 指向操作系统Monitor的指针
  1. 64位差异:线程ID占54bit,开启指针压缩时Klass Pointer占32bit

三、锁升级全流程(绝对高频考点)

核心原则:锁只能升级不能降级,JDK1.6+默认开启偏向锁和轻量级锁

  1. 偏向锁(单线程无竞争)
    • 设计思想:让锁偏向第一个获取它的线程,后续获取无需CAS
    • 获取:CAS将Mark Word中的线程ID替换为当前线程ID
    • 升级触发:有其他线程竞争该锁
    • 关键:偏向锁撤销需要全局安全点(STW)
  2. 轻量级锁(多线程交替执行)
    • 设计思想:用用户态CAS代替内核态阻塞,避免线程切换开销
    • 获取:线程栈创建锁记录→复制Mark Word到锁记录→CAS将对象头指向锁记录
    • 升级触发:自旋失败(默认10次)或多个线程同时竞争
    • 关键:自旋会消耗CPU,适用于锁持有时间极短的场景
  3. 重量级锁(多线程同时竞争)
    • 设计思想:依赖操作系统互斥量,竞争失败的线程直接阻塞
    • 实现:对象头指向ObjectMonitor,包含_owner、_count、_entryList、_waiters
    • 缺点:线程阻塞和唤醒需要内核态/用户态切换,开销大

四、JVM锁优化技术

  1. 自适应自旋:自旋次数不固定,由前一次自旋成功率和锁持有者状态决定
  2. 锁消除:基于逃逸分析,消除不可能存在竞争的锁(如局部变量的锁)
  3. 锁粗化:将多个连续的加锁解锁操作合并为一个大锁,减少频繁操作开销

五、常见误区澄清

  1. ❌ synchronized是重量级锁 → ✅ 无竞争时是偏向锁,性能极高
  2. ❌ 锁可以降级 → ✅ 只能升级,偏向锁撤销≠降级
  3. ❌ 静态方法和实例方法互斥 → ✅ 锁对象不同,互不影响
  4. ❌ 自旋一定比阻塞好 → ✅ 锁持有时间长时,自旋会浪费大量CPU

3道高频面试题及标准答案

面试题1:详细描述synchronized的锁升级全过程

标准答案
synchronized在JDK1.6后引入了分级锁机制,锁会按照偏向锁→轻量级锁→重量级锁的顺序逐步升级:

  1. 初始状态:对象刚创建时处于无锁可偏向状态,Mark Word中偏向位为1,线程ID为空。
  2. 偏向锁获取:当第一个线程访问同步块时,通过CAS操作将Mark Word中的线程ID设置为自己的ID。如果成功,该线程后续每次进入同步块都无需任何同步操作。
  3. 偏向锁撤销与升级:当有第二个线程竞争该锁时,会触发偏向锁撤销。JVM会暂停持有偏向锁的线程,检查其是否还在执行同步块:
    • 如果线程已退出同步块,将对象头恢复为无锁状态,重新偏向新线程
    • 如果线程仍在执行同步块,立即升级为轻量级锁
  4. 轻量级锁获取:线程在自己的栈帧中创建锁记录,将对象头的Mark Word复制到锁记录中,然后通过CAS尝试将对象头的Mark Word替换为指向自己锁记录的指针。如果成功,获取轻量级锁。
  5. 轻量级锁升级:如果CAS失败,说明存在竞争,线程会自旋等待一定次数。如果自旋结束仍未获取到锁,或者有第三个线程加入竞争,立即升级为重量级锁。
  6. 重量级锁运行:升级后,对象头指向操作系统的Monitor对象。竞争失败的线程会被操作系统挂起,进入阻塞队列。当锁被释放时,操作系统会唤醒所有等待线程重新竞争。

面试题2:synchronized和ReentrantLock有什么区别?

标准答案
两者都是可重入锁,主要区别如下:

维度 synchronized ReentrantLock
实现层面 JVM原生实现,由C++编写 JDK API实现,纯Java代码
锁释放 自动释放,异常时JVM会自动执行monitorexit 必须手动在finally块中调用unlock(),否则会造成死锁
公平性 仅支持非公平锁 支持公平锁和非公平锁(构造方法传入true开启公平锁)
高级特性 不支持中断、超时获取,仅支持一个条件变量(wait/notify) 支持lockInterruptibly()中断等待、tryLock()超时获取、支持多个Condition条件变量
性能 JDK1.6+优化后,低竞争场景下与ReentrantLock相当 高竞争场景下性能更稳定,可控性更强

选型建议

  • 优先使用synchronized,语法简单,不易出错,JVM会持续优化
  • 当需要公平锁、可中断、超时获取或多个条件变量时,使用ReentrantLock

面试题3:什么是偏向锁?它的撤销过程是怎样的?为什么需要STW?

标准答案

  1. 偏向锁定义:偏向锁是JDK1.6引入的一种锁优化,针对的是锁在大多数情况下只会被同一个线程多次获取的场景。它让锁偏向于第一个获取它的线程,后续该线程获取锁时无需任何CAS操作,几乎没有开销。

  2. 偏向锁撤销过程

    • 当有其他线程尝试获取偏向锁时,会触发撤销操作
    • JVM会首先等待全局安全点(STW),暂停所有正在执行的用户线程
    • 检查持有偏向锁的线程是否还在执行同步块:
      • 如果线程已经退出同步块,将对象头的Mark Word恢复为无锁可偏向状态,然后让新线程重新偏向
      • 如果线程仍在执行同步块,说明存在真正的竞争,将锁升级为轻量级锁
    • 恢复所有用户线程的执行
  3. 为什么需要STW
    偏向锁的撤销需要修改对象头的Mark Word,而这个修改必须是原子的。如果不暂停所有线程,可能会出现多个线程同时修改Mark Word的情况,导致数据不一致。因此必须在全局安全点暂停所有线程,确保没有任何线程正在访问对象头,才能安全地进行修改。

相关文章
|
9天前
|
人工智能 开发工具 iOS开发
Claude Code 新手完全上手指南:安装、国产模型配置与常用命令全解
Claude Code 是一款运行在终端环境中的 AI 编程助手,能够直接在命令行中完成代码生成、项目分析、文件修改、命令执行、Git 管理等开发全流程工作。它最大的特点是**任务驱动、终端原生、轻量高效、多模型兼容**,无需图形界面、不依赖 IDE 插件,能够深度融入开发者日常工作流。
3136 8
|
12天前
|
Shell API 开发工具
Claude Code 快速上手指南(新手友好版)
AI编程工具卷疯啦!Claude Code凭借任务驱动+终端原生的特性,成了开发者的效率搭子。本文从安装、登录、切换国产模型到常用命令,手把手带新手快速上手,全程避坑,30分钟独立用起来。
3198 20
|
5天前
|
人工智能 Linux BI
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
JeecgBoot AI专题研究 一键脚本:Claude Code + JeecgBoot Skills + DeepSeek 全平台接入 一行命令装好 Claude Code + JeecgBoot Skills + DeepSeek 接入,无需翻墙使用 Claude Code,支持 Wind
2130 3
国内用 Claude Code 终于不用翻墙了:一行命令搞定,自动接 DeepSeek
|
24天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23591 15
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
|
1天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队版、Coding Plan或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
|
11天前
|
人工智能 JSON BI
DeepSeek V4-Pro 接入 Claude Code 完全实战:体验、测试与关键避坑指南
Claude Code 作为当前主流的 AI 编程辅助工具,凭借强大的代码理解、工程执行与自动化能力深受开发者喜爱,但原生模型的使用成本相对较高。为了在保持能力的同时进一步降低开销,不少开发者开始寻找兼容度高、价格更友好的替代模型。DeepSeek V4 系列的发布带来了新的选择,该系列包含 V4-Pro 与 V4-Flash 两款模型,并提供了与 Anthropic 完全兼容的 API 接口,理论上只需简单修改配置,即可让 Claude Code 无缝切换为 DeepSeek 引擎。
2645 3
|
3天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全+三种模式+记忆体系+实战工作流完整手册
Claude Code 是当前最流行的终端级 AI 编程助手,能够直接在命令行中完成代码生成、项目理解、文件修改、命令执行、错误修复等全流程开发工作。它不依赖图形界面、不占用额外资源,却能深度理解项目结构,自动生成规范代码,大幅提升研发效率。
772 2
|
10天前
|
人工智能 安全 开发工具
Claude Code 官方工作原理与使用指南
Claude Code 不是传统代码补全工具,而是 Anthropic 推出的终端 AI 代理,具备代理循环、双驱动架构(模型+工具)、全局项目感知、6 种权限模式等核心能力,本文基于官方文档系统解析其工作原理与高效使用技巧。
1443 0

热门文章

最新文章