深入理解 synchronized

引言:synchronized的核心地位

在Java并发编程中,synchronized关键字是实现线程安全的基石。自JDK 1.0引入以来,它经历了从"重量级锁"到"自适应锁"的进化,如今已成为兼顾安全性与性能的成熟方案。本文将从用法解析字节码实现底层原理锁升级机制JDK优化性能对比最佳实践,全方位剖析synchronized的技术细节,结合OpenJDK源码与实测数据,带你彻底掌握这一并发利器。

一、synchronized的基本用法与语义

1.1 三种使用方式

synchronized可修饰方法或代码块,核心是通过对象锁实现线程互斥。具体用法如下:

用法场景锁对象字节码实现示例代码
修饰实例方法当前对象实例(this方法访问标志ACC_SYNCHRONIZEDpublic synchronized void increment() { count++; }
修饰静态方法类对象(Class实例)方法访问标志ACC_SYNCHRONIZEDpublic static synchronized void staticIncrement() { staticCount++; }
修饰代码块显式指定对象monitorenter/monitorexit指令synchronized (lockObj) { count++; }

关键语义

  • 互斥性:同一时刻只有一个线程能持有锁,确保临界区代码串行执行。
  • 可见性:释放锁时,线程会将工作内存中的修改刷新到主内存;获取锁时,线程会失效本地缓存,从主内存加载最新值(通过内存屏障实现)。
  • 可重入性:线程可重复获取已持有的锁,通过_recursions计数器实现(见ObjectMonitor源码)。

1.2 用法示例与字节码分析

示例1:同步代码块
public class SyncBlockExample {private final Object lock = new Object();private int count = 0;public void increment() {synchronized (lock) { // 显式指定lock为锁对象count++;}}
}

字节码反编译javap -v SyncBlockExample.class):
同步代码块通过monitorenter(进入锁)和monitorexit(释放锁)指令实现:

  public void increment();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=3, args_size=10: aload_01: getfield      #2                  // Field lock:Ljava/lang/Object;4: dup5: astore_1                          // 将锁对象引用存入局部变量表6: monitorenter                      // 获取锁7: aload_08: dup9: getfield      #3                  // Field count:I12: iconst_113: iadd14: putfield      #3                  // Field count:I17: aload_118: monitorexit                       // 正常退出时释放锁19: goto          2722: astore_223: aload_124: monitorexit                       // 异常退出时释放锁25: aload_226: athrow27: return

注意:编译器会生成两个monitorexit,分别对应正常退出和异常退出,确保锁必定释放。

示例2:同步方法
public class SyncMethodExample {private int count = 0;public synchronized void increment() { // 实例方法锁,锁对象为thiscount++;}public static synchronized void staticIncrement() { // 静态方法锁,锁对象为SyncMethodExample.classstaticCount++;}
}

字节码特征:同步方法通过ACC_SYNCHRONIZED标志实现,无需显式monitor指令:

  public synchronized void increment();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZED  // 同步方法标志Code:stack=3, locals=1, args_size=10: aload_01: dup2: getfield      #2                  // Field count:I5: iconst_16: iadd7: putfield      #2                  // Field count:I10: return

二、底层实现:对象头与Monitor机制

2.1 对象头与Mark Word

synchronized的实现依赖对象头(Object Header)中的Mark Word存储锁状态。对象头由两部分组成:

  • Mark Word:存储对象运行时数据(哈希码、GC年龄、锁状态等)。
  • Klass Pointer:指向类元数据的指针。

64位JVM Mark Word格式(不同锁状态下的存储结构):

锁状态标志位存储内容
无锁01哈希码(25bit)+ GC年龄(4bit)+ 是否偏向锁(1bit=0)+ 标志位(2bit=01)
偏向锁01偏向线程ID(54bit)+ Epoch(2bit)+ GC年龄(4bit)+ 是否偏向锁(1bit=1)+ 标志位(2bit=01)
轻量级锁00指向栈中锁记录(Lock Record)的指针(64bit)+ 标志位(2bit=00)
重量级锁10指向ObjectMonitor对象的指针(64bit)+ 标志位(2bit=10)
GC标记11

工具推荐:使用org.openjdk.jol:jol-core查看对象头,如ClassLayout.parseInstance(obj).toPrintable()

2.2 Monitor监视器锁

重量级锁的实现依赖ObjectMonitor(C++实现),每个对象关联一个Monitor,用于管理线程竞争与等待。

ObjectMonitor核心结构(OpenJDK源码objectMonitor.hpp):

class ObjectMonitor {
private:void* volatile _owner;       // 持有锁的线程ObjectWaiter* volatile _WaitSet; // 等待队列(调用wait()的线程)ObjectWaiter* volatile _EntryList; // 阻塞队列(未获取锁的线程)int _recursions;             // 重入次数int _count;                  // 等待线程数// ...其他字段
};

工作流程

  1. 竞争锁:线程通过CAS尝试将_owner设为自身,成功则获取锁;失败则进入_EntryList阻塞。
  2. 释放锁:线程退出同步块时,将_owner设为null,唤醒_EntryList中的线程重新竞争。
  3. 等待/唤醒:调用wait()时,线程释放锁并进入_WaitSetnotify()将线程从_WaitSet移至_EntryList重新竞争。

三、锁升级机制:从偏向锁到重量级锁

JDK 1.6引入锁升级机制,根据竞争程度动态选择锁状态(不可逆),平衡性能与安全性。

3.1 偏向锁(Biased Locking)

设计目标:减少单线程重复获取锁的开销。
实现原理

  • 首次获取锁时,通过CAS将线程ID记录到Mark Word,设为偏向模式(标志位101)。
  • 后续同一线程访问时,仅需比对线程ID,无需CAS操作。

撤销条件:当其他线程尝试竞争时,需等待全局安全点(STW),暂停持有线程,检查状态:

  • 若持有线程已结束,重置为无锁状态。
  • 若持有线程存活,升级为轻量级锁。

JVM参数

  • -XX:+UseBiasedLocking(默认启用,JDK 15后默认禁用)。
  • -XX:BiasedLockingStartupDelay=0(禁用启动延迟)。

3.2 轻量级锁(Lightweight Locking)

设计目标:应对多线程交替执行的轻度竞争。
实现步骤

  1. 创建锁记录:线程在栈帧中创建Lock Record,复制Mark Word(Displaced Mark Word)。
  2. CAS竞争锁:通过CAS将Mark Word替换为指向Lock Record的指针(标志位00)。
  3. 自旋重试:竞争失败时,线程自旋(空循环)尝试获取锁,避免阻塞(自适应自旋:根据历史成功率调整次数)。

升级条件

  • 自旋超过阈值(默认10次,JDK 1.7后自适应)。
  • 竞争线程数超过CPU核心数一半。

3.3 重量级锁(Heavyweight Locking)

设计目标:应对高并发激烈竞争。
实现原理

  • Mark Word指向ObjectMonitor,未获取锁的线程进入_EntryList阻塞(操作系统级别的互斥锁)。
  • 线程阻塞/唤醒涉及用户态→内核态切换,开销较大。

性能对比

锁状态获取成本释放成本适用场景
偏向锁极低极低单线程重复访问
轻量级锁低(CAS)低(CAS)多线程交替执行
重量级锁多线程同时竞争

四、JDK优化:从锁消除到虚拟线程

4.1 锁优化技术

锁消除(Lock Elimination)

JIT编译器通过逃逸分析,消除不可能存在竞争的锁。例如:

public String concat(String a, String b) {StringBuffer sb = new StringBuffer(); // StringBuffer的append是同步方法sb.append(a).append(b);return sb.toString();
}
// 逃逸分析发现sb未逃逸,消除同步锁
锁粗化(Lock Coarsening)

合并连续的锁申请,减少锁竞争频率:

for (int i = 0; i < 1000; i++) {synchronized (lock) { // 循环内频繁加锁,粗化为一次锁申请count++;}
}
// 优化后:synchronized (lock) { for (...) { count++; } }
自适应自旋(Adaptive Spinning)

JVM根据历史自旋成功率动态调整次数:

  • 若自旋成功,下次增加自旋次数(最大100次)。
  • 若自旋失败,减少或省略自旋,避免CPU空转。

4.2 JDK 17的虚拟线程支持

JDK 17通过JEP 491优化synchronized与虚拟线程(Virtual Threads)的兼容性,避免线程固定(Pinning):

  • 虚拟线程阻塞于synchronized时,JVM自动卸载载体线程(Carrier Thread),允许其他虚拟线程复用。
  • 实现原理:结合Continuation机制,在阻塞时保存栈帧,释放载体线程。

性能提升:在高并发I/O场景,吞吐量提升30%+,避免传统线程阻塞导致的资源浪费。

五、源码深度剖析:ObjectMonitor关键方法

5.1 加锁(enter方法)

void ATTR ObjectMonitor::enter(TRAPS) {Thread* Self = THREAD;void* cur = Atomic::cmpxchg_ptr(Self, &_owner, NULL); // CAS尝试获取锁if (cur == NULL) { // 成功获取锁,_owner = Selfreturn;}if (cur == Self) { // 重入,_recursions++_recursions++;return;}// 竞争失败,进入自旋或阻塞if (Knob_SpinEarly && TrySpin(Self) > 0) { // 自旋成功,获取锁return;}// 自旋失败,进入_EntryList阻塞ThreadBlockInVM tbivm(Self);Self->set_current_pending_monitor(this);EnterI(Self); // 进入阻塞队列
}

5.2 释放锁(exit方法)

void ATTR ObjectMonitor::exit(TRAPS) {if (_recursions != 0) { // 重入解锁,_recursions--_recursions--;return;}// 唤醒_EntryList中的线程ObjectWaiter* w = NULL;w = _EntryList;if (w != NULL) {Atomic::cmpxchg_ptr(NULL, &_owner, Self); // 释放锁OrderAccess::fence();WakeupWaiter(w); // 唤醒线程return;}// 无等待线程,直接释放Atomic::cmpxchg_ptr(NULL, &_owner, Self);
}

六、性能对比与最佳实践

6.1 synchronized vs Lock(ReentrantLock)

特性synchronizedReentrantLock
实现层级JVM层面(关键字)JDK层面(接口)
锁释放自动释放(异常/正常退出)手动释放(需finally块)
高级功能无(不可中断、非公平)支持中断、超时、公平锁、条件变量
性能(低竞争)接近(偏向锁/轻量级锁)略高(CAS操作)
性能(高竞争)重量级锁开销大略优(队列优化)

建议:简单同步需求用synchronized(简洁安全);需高级功能(如超时获取)用ReentrantLock

6.2 生产环境最佳实践

  1. 减小锁粒度:同步代码块仅包裹临界区,避免锁范围过大:

    // 反例:整个方法加锁
    public synchronized void process() {readConfig(); // 无需同步的操作updateState(); // 需同步的临界区
    }
    // 正例:仅临界区加锁
    public void process() {readConfig();synchronized (lock) {updateState();}
    }
    
  2. 避免嵌套锁:减少死锁风险,如必须嵌套,确保锁顺序一致。

  3. 禁用偏向锁:高并发场景下,偏向锁撤销开销大,通过-XX:-UseBiasedLocking禁用。

  4. 监控锁状态:使用jstack查看线程阻塞状态,定位竞争热点:

    jstack <pid> | grep -A 20 "BLOCKED"  # 查看阻塞线程
    
  5. JVM参数调优

    • -XX:PreBlockSpin=10:轻量级锁自旋次数。
    • -XX:BiasedLockingBulkRebiasThreshold=20:批量重偏向阈值。
    • -XX:BiasedLockingBulkRevokeThreshold=40:批量撤销阈值。

七、总结

synchronized从早期的重量级锁进化为如今的自适应锁机制,体现了JVM对性能的极致追求。其核心在于动态锁升级(偏向锁→轻量级锁→重量级锁),结合对象头Mark Word与ObjectMonitor实现高效同步。在JDK 17中,通过对虚拟线程的支持,进一步提升了高并发场景下的可扩展性。

掌握synchronized的底层原理,不仅能写出更高效的并发代码,更能深入理解JVM的优化机制。在实际开发中,需结合业务场景选择合适的锁策略,平衡安全性与性能,避免过度同步或锁竞争导致的性能瓶颈。

参考资料

  • OpenJDK源码(hotspot/src/share/vm/runtime/objectMonitor.hpp)
  • 《深入理解Java虚拟机》(周志明)
  • JDK官方文档(JEP 142、JEP 491)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/89931.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/89931.shtml
英文地址,请注明出处:http://en.pswp.cn/web/89931.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言字符串相关函数

C语言笔记内容提要数组字符串基本操作字符串相关函数综合案例&#xff1a;学生成绩管理系统数组字符串基本操作在用格式化说明符%s进行输入输出时&#xff0c;其输入输出项均为数组名。但在输入时&#xff0c;相邻两个字符串之间要用空格分隔&#xff0c;系统将自动在字符串后加…

从零开始:用Python库轻松搭建智能AI代理

为什么要关注AI代理&#xff1f; “Agentic AI”&#xff08;智能代理&#xff09;正在悄然改变我们的工作方式。想象一下&#xff0c;一个AI助手不仅能帮你查航班、订机票&#xff0c;还能自动安排行程、发邮件、生成日报——就像一个效率极高的“虚拟助理”团队。 对于测试工…

如何防止GitHub上的敏感信息被泄漏?

如大家所了解的&#xff0c;随着GitHub的用户越来越多&#xff0c;GitHub上的敏感信息被泄漏的问题也越来越严重。那么如何做&#xff0c;才能防止此类事情发生呢&#xff1f;这值得我们探讨。移除并删除敏感信息当我们发现了历史 commit 中包含敏感信息后&#xff0c;第一步便…

船舶机械零件的深孔工艺及检测方法 —— 激光频率梳 3D 轮廓检测

引言船舶机械零件中的深孔结构&#xff08;深径比&#xff1e;15:1&#xff09;直接影响动力系统可靠性&#xff0c;如柴油机缸体深孔、推进轴系润滑油孔等。此类深孔具有孔径大&#xff08;φ10 - 50mm&#xff09;、深度深&#xff08;500 - 2000mm&#xff09;、表面质量要求…

论文Review Lidar 3DGS Splat-LOAM: Gaussian Splatting LiDAR Odometry and Mapping

基本信息 题目&#xff1a;Splat-LOAM: Gaussian Splatting LiDAR Odometry and Mapping 来源&#xff1a;ICCV 2025 学校&#xff1a;Sapienza University of Rome 是否开源&#xff1a;https://github.com/rvp-group/Splat-LOAM 摘要&#xff1a;纯激光3DGS&#xff01;…

MYSQL:数据库约束

文章目录MYSQL&#xff1a;数据库约束&#xff1a;为你的数据上把“安全锁”1. 约束的类型概览2. NOT NULL 非空约束3. DEFAULT 默认值约束4. UNIQUE 唯一约束5. PRIMARY KEY 主键约束5.1 自增主键 AUTO_INCREMENT5.3 复合主键6. FOREIGN KEY 外键约束7. CHECK 约束总结MYSQL&a…

网络数据编码技术及其应用场景的全面解析

网络数据编码技术全景图​编码类型​​编码原理​​适用层​​典型应用场景​​优势​​缺陷​​曼彻斯特编码​电平跳变代表数据位&#xff08;高→低1&#xff0c;低→高0&#xff09;物理层10/100M以太网、RFID标签自同步时钟带宽利用率仅50%​4B/5B编码​4比特映射为5比特物…

RustDesk自建服务器完整部署指南:从零开始到成功连接。成功解决rustdesk报错:未就绪,请检查网络连接

最近需要用到远程工具解决用户问题&#xff0c;todesk总是提示付费&#xff0c;干脆自己使用开源的。当然凡事都有代价。 话费了一个工作日的时间终于搞定了。 本文将详细介绍如何从零开始部署RustDesk自建服务器&#xff0c;实现完全自主可控的远程桌面解决方案。 踩坑 参考…

datasophon安装doris问题排除记录

datasophon安装doris搞了好久才成功&#xff0c;特别记录一下。 多灾多难的安装过程&#xff1a;FE安装 首先&#xff0c;配置界面&#xff0c;要注意两个参数一定要改成正确的网段&#xff0c;否则会被识别成127.0.0.1注意&#xff1a;两个priority_networks 参数必须要改成你…

suricata新增Mysql告警规则处理

suricata新增Mysql告警规则处理协议解析后续处理内容新增规则规则解析关键字新增Setup用于初始化检测项Free用于资源释放AppLayerTxMatch用于协议解析完成后的规则检测针对pcap文件进行检测总结协议解析后续处理内容 经过Mysql协议解析处理流程 介绍&#xff0c;我们在suricat…

使用位运算优化 Vue.js 应用:高效状态管理技巧

在 Vue.js 开发中&#xff0c;位运算&#xff08;Bitwise Operations&#xff09;是一种高效的工具&#xff0c;尤其适用于需要管理大量布尔状态或优化性能的场景。位运算通过操作二进制位来实现状态的存储和检查&#xff0c;相比传统的数组或对象操作&#xff0c;内存占用更低…

【Java SE】Clonable接口和深拷贝

目录 一.Clonable接口 实现步骤&#xff1a; 完整代码&#xff1a; 二.深拷贝 实现步骤&#xff1a; 完整代码&#xff1a; 浅拷贝与深拷贝的对比 使用场景建议 完 浅拷贝&#xff08;Shallow Copy&#xff09;和深拷贝&#xff08;Deep Copy&#xff09;是对象复制的两…

accelerate 在Pycham中执行的设置方法

背景 使用 accelerate 进行分布式代码训练时&#xff0c;需要在pycharm中进行调试&#xff0c;此时需要在pycharm中运行。 终端执行命令 # *[Specify the config file path and the GPU devices to use] export CUDA_VISIBLE_DEVICES0# *[Specify the config file path] expo…

探索量子计算与法律理论的交叉领域

文章目录 前言 一、引言 二、内容 (一)知识产权 (二)隐私与安全 (三)责任认定 (四)证据与证明 (五)法律推理与决策 三、结论 总结 前言 随着量子计算技术的突破性发展,其引发的法律范式重构问题日益凸显。乌兹别克斯坦学者伊索姆别克・阿卜迪哈基莫夫于2024年在《量…

js迭代器

文章目录前言实现原理&#xff1a;调用迭代器自制迭代器前言 迭代器是 JSt 中一种特殊的对象&#xff0c;它提供了一种统一的、通用的方式遍历个各种不同类型的数据结构。 可以遍历的数据结构包括&#xff1a;数组、字符串、Set、Map 等可迭代对象。我们也可以自定义实现迭代器…

chainlink VRF中文教程(含mock),解决error: Arithmetic Underflow in createSubscription

⸻我使用的版本&#xff1a;chainlink-brownie-contracts version:1.3.0⸻1. Import 相关包 ,,, import {VRFConsumerBaseV2Plus} from "chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2PLUS.sol"; import {VRFV2PlusClient} from "chainlink/contract…

非线性优化框架CasADi工具箱求解最优控制问题OCP

CasADi是一个开源的Python/MATLAB库&#xff0c;主要用于数值优化&#xff0c;特别是最优控制问题。它提供了一个易于使用的符号框架&#xff0c;用于处理和生成表达式&#xff0c;以及高效地生成导数信息。 https://web.casadi.org/get/https://web.casadi.org/get/ 所有OCP…

Type-C接口台式显示器:LDR6021引领新潮流

Type-C单口便携显示器LDR6021是市场上一种新兴的显示设备&#xff0c;以下是对其的详细介绍一、主要特点 便携性:LDR6021采用了Type-C接口作为数据传输和供电接口&#xff0c;这种设计使得它能够与各种支持Type-C接口的设备无缝连接&#xff0c;如笔记本电脑、智能手机、平板电…

在翻译语义相似度和会议摘要相似度评估任务中 ,分类任务 回归任务 生成任务区别

在翻译语义相似度&#xff08;Translation Semantic Similarity&#xff09;和会议摘要相似度&#xff08;Meeting Summary Similarity&#xff09;等任务中&#xff0c;通常会根据任务的目标和输出形式&#xff0c;将其划分为三类常见的任务类型&#xff1a;1. 分类任务定义&a…

UGUI 性能优化系列:第二篇——Canvas 与 UI 元素管理

UGUI 性能优化系列&#xff1a;第一篇——基础优化与资源管理 UGUI 性能优化系列&#xff1a;第二篇——Canvas 与 UI 元素管理 UGUI 性能优化系列&#xff1a;第三篇——渲染与像素填充率优化 UGUI 性能优化系列&#xff1a;第四篇——高级优化与注意事项 在 UGUI 性能优化…