Java synchronized关键字详解:从入门到原理(两课时)

Java synchronized关键字详解:从入门到原理(两课时)
在这里插入图片描述

文章目录

在这里插入图片描述

适用对象

本课程适合具备Java基础语法知识,初步了解多线程概念但尚未系统学习并发编程的开发者。无论你是准备面试的求职者,还是希望提升并发编程能力的工程师,这门课程都将为你打下坚实基础。

学习目标

通过两个课时的系统学习,你将能够:

  • 掌握 synchronized的三种使用方式及其区别
  • 理解 synchronized解决线程安全问题的核心原理
  • 剖析 synchronized底层的Monitor机制和锁升级过程
  • 区分 synchronized与volatile、ReentrantLock的适用场景
  • 应对 面试中关于synchronized的高频问题

课程安排

  • 第一课时(约40分钟):基础概念、使用方式、特性详解
  • 第二课时(约50分钟):底层原理、锁升级机制、对比与实战

第一课时:synchronized基础与使用

1.1 从一个线程安全问题开始

让我们先看一个经典问题:两个线程同时对共享变量执行自增操作。

publicclassCounter{privateint count =0;publicvoidincrement(){ count++;// 不是原子操作!}publicintgetCount(){return count;}}// 测试代码publicclassTest{publicstaticvoidmain(String[] args)throwsInterruptedException{Counter counter =newCounter();Thread t1 =newThread(()->{for(int i =0; i <10000; i++) counter.increment();});Thread t2 =newThread(()->{for(int i =0; i <10000; i++) counter.increment();}); t1.start(); t2.start(); t1.join(); t2.join();System.out.println("最终结果: "+ counter.getCount());// 期望20000,实际小于20000}}

运行结果:多次执行,结果总是小于20000,有时甚至相差甚远。

问题根源count++看似一行代码,但在JVM中分解为三条指令:

  1. 从主内存读取count到工作内存(读)
  2. 对count进行加1操作(改)
  3. 将结果写回主内存(写)

当两个线程同时执行这三步时,可能发生交替执行,导致最后写入的值覆盖了之前的计算结果——这就是典型的竞态条件

1.2 synchronized是什么?

synchronized是Java提供的一个关键字,译为“同步”。它可以保证在同一时刻,最多只有一个线程执行被它修饰的代码段,从而解决并发环境下的线程安全问题。

从专业角度说,synchronized实现了互斥访问,它保证了三个关键特性:

特性说明
原子性被保护的代码块要么全部执行,要么完全不执行,不会被打断
可见性一个线程修改共享变量后,其他线程能立即看到最新值
有序性禁止编译器和处理器对同步块内的代码进行指令重排序

1.3 初识synchronized的三种用法

synchronized的使用非常灵活,可以根据需要选择不同的加锁对象。下面我们通过三个典型场景来理解它们的区别。

1.3.1 修饰实例方法
publicclassSyncMethodDemo{privateint count =0;// synchronized修饰实例方法,锁是当前对象thispublicsynchronizedvoidincrement(){ count++;}}

特点

  • 锁对象是调用该方法的实例对象(即this)
  • 同一个对象的多个synchronized实例方法,共用同一把锁
  • 不同对象的方法互不干扰

示意图

线程A ──> obj1.increment() ──> 获取obj1锁 ──> 执行 线程B ──> obj1.increment() ──> 等待obj1锁 线程C ──> obj2.increment() ──> 获取obj2锁 ──> 执行(与A不冲突) 
1.3.2 修饰静态方法
publicclassSyncStaticDemo{privatestaticint count =0;// synchronized修饰静态方法,锁是当前类的Class对象publicstaticsynchronizedvoidincrement(){ count++;}}

特点

  • 锁对象是当前类的Class对象(如SyncStaticDemo.class)
  • 所有实例对象共享同一把类锁
  • 静态方法锁与实例方法锁互不干扰

思考题:如果一个类既有synchronized静态方法,又有synchronized实例方法,两个线程分别调用它们,会互斥吗?

  • 答案:不会。因为一个是类锁,一个是对象锁,两把不同的锁。
1.3.3 修饰代码块
publicclassSyncBlockDemo{privateObject lock =newObject();privateint count =0;publicvoidincrement(){// 同步代码块,锁是指定的lock对象synchronized(lock){ count++;}}publicvoiddecrement(){// 也可以锁thissynchronized(this){ count--;}}}

特点

  • 锁对象可以任意指定,灵活性最高
  • 可以精确控制同步范围,提高并发性能
  • 常用锁对象:this、自定义锁对象、类.class

1.4 深入理解锁的范围

1.4.1 三种锁的对比表格
使用形式锁对象作用范围典型应用场景
修饰实例方法当前实例对象单个实例内保护实例变量
修饰静态方法当前类的Class对象所有实例间保护静态变量
修饰代码块指定的任意对象代码块内细粒度控制
1.4.2 常见面试题解析

问题1:synchronized修饰代码块可以给类加锁吗?
当然可以!只要在括号内传入类名.class即可。例如:

synchronized(SyncBlockDemo.class){// 这是类锁}

问题2:构造方法可以用synchronized修饰吗?
不可以!Java语法规定构造方法不能是同步的。实际上,构造方法本身就是线程安全的,因为JVM会保证在对象初始化完成前,其他线程无法访问该对象。

问题3:静态同步方法和非静态同步方法同时调用会互斥吗?
不会互斥。假设线程A调用实例对象的非静态同步方法,线程B同时调用该对象所属类的静态同步方法,它们持有的是两把不同的锁(对象锁 vs 类锁),因此可以并行执行。

1.5 synchronized的核心特性

1.5.1 可重入性

概念:同一个线程在持有锁的情况下,可以再次获取同一把锁,而不会被阻塞。

publicclassReentrantDemo{publicsynchronizedvoidmethodA(){System.out.println("进入methodA");methodB();// 直接调用,不会死锁}publicsynchronizedvoidmethodB(){System.out.println("进入methodB");}}

为什么需要可重入?
如果没有可重入性,线程在调用methodA获取锁后,调用methodB时发现自己还要申请同一把锁,就会形成死锁。这显然不合理。

实现原理:每个锁关联一个持有线程和一个计数器。线程第一次获取锁时,计数器置为1;同一线程再次获取时,计数器递增;释放一次,计数器递减。直到计数器归零,锁才真正释放。

1.5.2 可见性保证

synchronized不仅能保证原子性,还能保证内存可见性。JMM(Java内存模型)规定:

  • 线程释放锁时,会将工作内存中的共享变量刷新到主内存
  • 线程获取锁时,会清空工作内存,从主内存重新读取共享变量

这就确保了:一个线程修改的共享变量,在释放锁后,其他线程能立即看到最新值。

1.6 第一课时小结

  • synchronized是Java内置的同步关键字,解决原子性、可见性、有序性问题
  • 三种使用方式:实例方法(锁this)、静态方法(锁Class对象)、代码块(锁任意对象)
  • 核心特性:可重入性、可见性、互斥性
  • 不同锁对象决定了不同的同步范围

第二课时:synchronized原理与优化

2.1 从字节码看synchronized的本质

2.1.1 同步代码块的字节码

先看一段简单的同步代码块:

publicclassSyncCodeBlock{publicvoiddoSomething(){synchronized(this){System.out.println("hello");}}}

使用javap -c -v反编译后,关键字节码如下:

 3: monitorenter // 进入同步块,获取监视器锁 4: getstatic // 调用System.out 7: ldc // 加载字符串 9: invokevirtual // 调用println 12: aload_1 13: monitorexit // 退出同步块,释放监视器锁 14: goto 22 // 正常结束跳转 17: astore_2 // 异常处理开始 18: aload_1 19: monitorexit // 异常时也释放锁 20: aload_2 21: athrow // 抛出异常 22: return 

关键发现

  • 同步代码块使用monitorentermonitorexit指令
  • 有两个monitorexit:一个正常退出,一个异常退出,确保锁一定被释放
  • 这就是为什么synchronized即使抛出异常也不会死锁
2.1.2 同步方法的字节码
publicsynchronizedvoidsyncMethod(){System.out.println("hello");}

反编译结果:

public synchronized void syncMethod(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED // 注意这个标志 Code: 0: getstatic #2 3: ldc #3 5: invokevirtual #4 8: return 

同步方法没有monitorenter/monitorexit指令,而是通过方法表flags中的ACC_SYNCHRONIZED标识。JVM根据这个标识判断是否需要获取锁。

结论:两种实现方式本质上都是获取对象的监视器锁(Monitor Lock),只是表现形式不同。

2.2 Monitor机制深度剖析

2.2.1 什么是Monitor?

Monitor(监视器)是操作系统中用于实现线程同步的机制。Java中的每个对象都与一个Monitor关联,这个关联关系存储在对象头的Mark Word中。

可以把Monitor理解为一个“接待室”,里面有三个关键区域:

 ┌─────────────────────┐ │ Owner │ ← 当前持有锁的线程 ├─────────────────────┤ │ EntryList │ ← 等待获取锁的线程队列 ├─────────────────────┤ │ WaitSet │ ← 调用了wait()的线程队列 └─────────────────────┘ 
2.2.2 Monitor的核心数据结构

在HotSpot虚拟机中,Monitor由C++的ObjectMonitor类实现,关键字段如下:

字段作用
_owner指向当前持有锁的线程
_EntryList等待获取锁的线程队列
_WaitSet调用了wait()的线程队列
_recursions记录锁的重入次数
_count记录线程获取锁的次数
2.2.3 锁获取和释放的完整流程

以两个线程T0和T1竞争锁为例:

步骤1:线程T0尝试获取锁

  • 根据对象头找到对应的ObjectMonitor
  • 检查_owner是否为null
  • 通过CAS操作将_owner设为T0,_count设为1
  • T0获取锁成功,进入同步块执行

步骤2:线程T1尝试获取锁

  • 此时_owner为T0,T1 CAS失败
  • T1会先自旋几次尝试(适应性自旋)
  • 如果T0很快释放锁,T1就成功获取
  • 如果T0未释放,T1进入_EntryList阻塞等待

步骤3:T0释放锁

  • 执行monitorexit_count减1
  • _count变为0,_owner设为null
  • 唤醒_EntryList中的线程(通常是队首线程)
  • T1被唤醒,重新竞争锁

2.3 锁升级:从偏向锁到重量级锁

早期Java的synchronized性能较差,被称为“重量级锁”。JDK 1.6之后引入了一系列优化,让synchronized的性能大幅提升。这就是著名的锁升级机制。

2.3.1 为什么需要锁升级?

想象一下这些场景:

  • 场景A:只有一个线程访问同步代码,根本不需要锁竞争
  • 场景B:两个线程交替访问,几乎没有同时竞争
  • 场景C:多个线程激烈竞争,需要操作系统级别的锁

如果用重量级锁统一处理所有场景,场景A和B会造成不必要的性能开销。锁升级就是让synchronized能够根据竞争激烈程度,动态调整锁的“重量”。

2.3.2 锁的四种状态

从低到高,锁有四种状态:

  1. 无锁状态
  2. 偏向锁
  3. 轻量级锁
  4. 重量级锁

锁可以升级,但不能降级(除了一次GC清理)。

2.3.3 偏向锁(Biased Locking)

适用场景:只有一个线程反复获取同一把锁。

原理

  • 第一次获取锁时,通过CAS将线程ID记录到对象头
  • 之后该线程再来时,只需检查对象头中是否是自己ID
  • 如果是,直接进入,无需任何同步操作

示例代码

publicclassBiasedLockDemo{privatestaticList<Integer> list =newVector<>();// Vector的方法都是同步的publicstaticvoidmain(String[] args){for(int i =0; i <100; i++){ list.add(i);// 同一线程反复获取锁}}}

优点:几乎没有加锁开销,性能极高。
缺点:一旦有其他线程尝试竞争,偏向锁立即撤销并升级。

注意:从JDK 15开始,偏向锁被标记为废弃,未来可能移除。因为在实际应用中,它的性能提升有限,且维护成本高。

2.3.4 轻量级锁(Lightweight Locking)

适用场景:两个线程交替执行同步块,没有真正的竞争。

原理

  • 线程在自己的栈帧中创建Lock Record,存储对象头的拷贝
  • 通过CAS尝试将对象头指向Lock Record
  • 如果成功,获取轻量级锁
  • 如果失败,说明有竞争,升级为重量级锁

轻量级锁的解锁

  • 通过CAS将对象头恢复为原Mark Word
  • 如果恢复成功,解锁完成
  • 如果失败,说明已经升级为重量级锁,走重量级锁解锁流程
2.3.5 重量级锁(Heavyweight Locking)

适用场景:多个线程激烈竞争。

原理

  • 线程进入_EntryList队列阻塞
  • 依赖操作系统Mutex Lock实现
  • 涉及用户态和内核态切换,开销较大
2.3.6 锁升级流程总结
 偏向锁 │ │ 有其他线程尝试获取 ↓ 轻量级锁 │ │ 有真实竞争 ↓ 重量级锁 

2.4 synchronized与volatile的对比

特性synchronizedvolatile
原子性✅ 保证❌ 不保证
可见性✅ 保证✅ 保证
有序性✅ 保证✅ 保证(禁止重排序)
用法修饰方法或代码块修饰变量
性能较重(但有优化)极轻
适用场景复合操作单一变量状态标志

volatile适用示例

publicclassVolatileDemo{privatevolatileboolean flag =true;// 只作为状态标志publicvoidstop(){ flag =false;// 简单写操作}publicvoidrun(){while(flag){// 执行任务}}}

2.5 synchronized与ReentrantLock的对比

2.5.1 对比表格
对比维度synchronizedReentrantLock
实现方式JVM关键字Java API(基于AQS)
锁释放自动释放必须手动unlock
公平性非公平可设置公平/非公平
可中断不支持支持lockInterruptibly()
超时获取不支持支持tryLock(timeout)
尝试获取不支持支持tryLock()
条件变量一个等待集多个Condition
锁状态查询不支持支持查询持有线程等
2.5.2 如何选择?

优先使用synchronized的情况:

  • 同步逻辑简单,不需要高级功能
  • 希望代码简洁不易出错
  • JVM持续优化,性能已很好

选择ReentrantLock的情况:

  • 需要公平锁
  • 需要尝试获取锁或超时获取
  • 需要可中断的锁获取
  • 需要多个条件变量(如生产者-消费者模式)
  • 高竞争场景需要更精细控制
2.5.3 ReentrantLock使用示例
ReentrantLock lock =newReentrantLock(true);// 公平锁Condition notFull = lock.newCondition();Condition notEmpty = lock.newCondition();// 标准使用模式 lock.lock();try{// 临界区代码while(条件不满足){ notEmpty.await();// 等待}// 执行操作 notFull.signal();}finally{ lock.unlock();// 必须释放}

2.6 实战:线程安全的单例模式

综合运用synchronized,实现几种经典的单例模式。

2.6.1 饿汉式(线程安全)
publicclassEagerSingleton{privatestaticfinalEagerSingleton INSTANCE =newEagerSingleton();privateEagerSingleton(){}publicstaticEagerSingletongetInstance(){return INSTANCE;}}
2.6.2 懒汉式(同步方法版)
publicclassLazySingleton{privatestaticLazySingleton instance;privateLazySingleton(){}publicstaticsynchronizedLazySingletongetInstance(){if(instance ==null){ instance =newLazySingleton();}return instance;}}
2.6.3 双重检查锁(DCL)
publicclassDCLSingleton{// volatile保证可见性和禁止重排序privatestaticvolatileDCLSingleton instance;privateDCLSingleton(){}publicstaticDCLSingletongetInstance(){if(instance ==null){synchronized(DCLSingleton.class){if(instance ==null){ instance =newDCLSingleton();}}}return instance;}}

为什么需要volatile?
instance = new DCLSingleton()不是原子操作,可能发生指令重排序。如果不加volatile,其他线程可能拿到一个未初始化完成的对象。这是DCL的关键细节。

2.7 第二课时小结

  • synchronized底层基于Monitor实现,字节码层面使用monitorenter/monitorexit或ACC_SYNCHRONIZED
  • 锁升级机制(偏向锁→轻量级锁→重量级锁)大幅提升了性能
  • synchronized保证原子性、可见性、有序性,是全面的同步工具
  • 与volatile相比,synchronized更重量但功能更全
  • 与ReentrantLock相比,synchronized简单易用,ReentrantLock功能更丰富
  • 实际开发中,根据场景选择合适的同步机制

课程总结

通过两个课时的学习,我们全面掌握了synchronized关键字:

  • 第一课时:从线程安全问题出发,学习了synchronized的三种使用方式、锁的范围、可重入性等基础概念
  • 第二课时:深入底层,剖析了Monitor机制、锁升级流程,并对比了volatile和ReentrantLock

synchronized作为Java最基础的同步工具,虽然简单,但背后的原理并不简单。理解它的实现机制,不仅能帮助我们写出更正确的并发程序,还能在面试中脱颖而出。

课后思考题

  1. 如果一个线程在同步块中抛出异常,锁会自动释放吗?为什么?
  2. 偏向锁在JDK 15中被废弃的原因是什么?谈谈你的理解。
  3. 如何用synchronized实现一个阻塞队列?
  4. 为什么说synchronized是“悲观锁”,而CAS是“乐观锁”?

Read more

必收藏!小白也能懂:Agent、Skills、MCP和A2A大模型架构完全指南

必收藏!小白也能懂:Agent、Skills、MCP和A2A大模型架构完全指南

文章详解AI Agent四大核心概念:Agent作为智能决策主体,Skills提供原子化能力封装,MCP实现标准化工具调用,A2A支持Agent间协作。这些技术共同构建了从单Agent自主执行到多Agent协同工作的完整技术栈,解决了智能体的自主性、模块化能力、工具调用和互操作等核心问题,助力开发者快速构建专业级AI应用。 一、Agent、Skills、MCP和A2A的核心概念总览 1、Agent (代理/智能体):自主决策与执行的“大脑”。 AI Agent是2026年AI生态的核心概念,是基于人工智能技术构建的、具备感知环境、理解信息、自主推理决策、自主规划与执行动作并持续与环境/其他主体交互,以自主达成预设或动态生成目标的数字智能实体。2026年的智能体不是在回答问题,而是在完成任务。其突破了传统问答式、生成式AI的能力边界,可像人类员工一样独立处理复杂综合性任务。它以大模型为核心引擎,整合规划、记忆、工具调用与行动执行四大能力,形成「感知 - 认知 - 决策 - 执行 - 反馈」的完整智能闭环,

By Ne0inhk
毕业设计不用愁:一个免费的 SQL 转 ER 图在线工具,真香!

毕业设计不用愁:一个免费的 SQL 转 ER 图在线工具,真香!

毕业设计不用愁:一个免费的 SQL 转 ER 图在线工具,真香! * 📌 工具地址(直接能用): * ✨ 工具亮点:一贴SQL,秒出ER图 * 🎯 使用方式示意 * 🧰 适用场景:毕设 + 课程设计 + 快速原型设计 * 🆓 其他可配套使用的工具(同一个网站): * 🧠 总结 * 💬 如果觉得实用,欢迎点个赞或收藏,后续我也会分享更多毕业设计 / 技术工具相关内容。也欢迎留言交流你在用哪些工具提升效率! 每到毕业季,很多计算机相关专业的同学都逃不过一个“老大难”任务:数据库设计文档。 建表语句写好了,项目也跑起来了,但老师或答辩组要求提交“ER 图”。这时候,不少同学陷入手动画图的深坑: 用 Visio 太复杂,PowerDesigner 要装半天,还容易报错……最后干脆拿 Excel 画框线,凑个样子就交了。 其实你完全可以用一个免费的在线工具,直接把 SQL

By Ne0inhk

Spring Cloud 概述

目录 微服务 单体架构 集群和分布式架构 横向扩展 纵向扩展 微服务架构 Spring Cloud 什么是 Spring Cloud Spring Cloud 版本 Spring Cloud 实现方案 服务拆分 服务拆分原则 简单示例 服务拆分 数据准备 工程搭建 父工程创建 子项目创建-商品服务 子项目创建-订单服务 远程调用 在学习 Spring Cloud 之前,我们先来了解一下什么是微服务,以及微服务的发展历史。架构发展的过程中,遇到了哪些问题?是如何解决的?Spring Cloud 解决了其中的什么问题? 微服务 单体架构 所有功能模块(如用户管理、订单处理)打包在一个应用中,共享同一数据库,模块间通过函数调用直接通信,开发、测试、

By Ne0inhk

ClawdBot保姆级教学:解决Gateway not reachable错误的5种方法

ClawdBot保姆级教学:解决Gateway not reachable错误的5种方法 1. 什么是ClawdBot?——你的本地AI助手,不是云端玩具 ClawdBot 是一个真正属于你自己的个人 AI 助手。它不依赖远程API、不上传隐私数据、不按调用次数收费——所有推理都在你自己的设备上完成。你可以把它理解成“装在你电脑里的 Siri + Copilot + Notion AI 的混合体”,但更自由、更透明、更可控。 它的核心能力由 vLLM 提供支撑。vLLM 是当前最高效的开源大模型推理引擎之一,以极高的吞吐量和极低的显存占用著称。ClawdBot 利用它来加载和运行像 Qwen3-4B-Instruct 这样的轻量级但能力扎实的模型,让你在消费级显卡(甚至带显存的笔记本)上也能获得接近专业服务的响应速度和对话质量。 和那些动辄要填 API Key、绑定手机号、看广告才能用的 Web 应用不同,ClawdBot 的哲学是:“你装,你用,

By Ne0inhk