当前位置: 首页 > news >正文

第一篇:只是想说清楚每行代码是由谁执行的,怎样执行的

一个简单的问题,一段复杂的旅程

强调一个共识:CPU执行速度极快,数据运算的指令执行极快,真正速度慢的是IO,所以理想状态下,并发甚至可被看作是并行

二十个线程执行二十个写数据库请求,每个请求10ms,理想状态下,最好状况是分别对不同行数据操作10ms(近似,由于CPU执行速度极快)执行完,最坏状况下对同一行数据操作由于行锁导致串行化200ms执行完

@ServicepublicclassOrderService{@AutowiredprivateOrderMapperorderMapper;publicvoidcreateOrder(Orderorder){orderMapper.insert(order);}}

这段代码再普通不过——你每天写几十个类似的Service方法。但如果我问你:

orderMapper.insert(order)这一行代码,是谁执行的?怎么执行的?”

你可能会说:“是CPU执行的。”

对。但CPU怎么知道要执行这行代码?代码怎么从Java文件变成CPU能识别的指令?数据库在远程服务器上,CPU怎么把数据送过去?送过去之后,这段时间CPU在干嘛?

这些问题看似基础,但真正能从头到尾讲清楚的人不多。本文的目标,就是把这一行代码从编写到执行的完整旅程讲清楚。不是为了背八股文,而是为了在面试被追问到"你写的代码到底是怎么跑的"时,你能从应用层一直讲到硬件层。


一、谁在写代码——你写的Java代码,CPU不认识

你写的是orderMapper.insert(order),这是一个Java方法调用。但CPU只认识机器指令——二进制的操作码和操作数。

Java代码要经过两层转换才能被CPU执行:

你写的Java代码(.java文件) ↓ 编译(javac) JVM字节码(.class文件)——Java虚拟机认识的中间语言 ↓ 解释执行 / JIT编译 机器指令——CPU直接认识的二进制指令

第一层转换是编译——javac把Java代码编译成字节码,存储在.class文件中。字节码是Java虚拟机的"指令集",它不是任何真实CPU的指令,而是JVM定义的一套中间语言。

第二层转换发生在运行时——JVM把字节码解释执行,或者通过JIT编译器把热点代码编译成本地机器指令直接交给CPU执行。HotSpot VM默认两者混合使用:先解释执行,发现某个方法被频繁调用后启用JIT编译优化。

所以,你写的代码,最终是由JVM翻译成机器指令,然后交给CPU执行的。Java代码本身永远不会被CPU直接执行——它必须经过这层翻译。


二、谁在执行——CPU是唯一能执行指令的硬件

现在代码变成了机器指令,谁来执行这些指令?

CPU。整个计算机里,只有CPU能执行指令。没有第二个硬件能做这件事。

硬盘只能存数据,网卡只能收发网络包,内存只能存临时数据——它们都不认识"指令",只认识"数据"。运行在硬件之上的操作系统、JVM、你的应用程序,最终都是CPU一刻不停地逐条取指令、执行指令,在指令流中完成一切操作。

但CPU不能直接访问硬盘——它通过内存和IO总线间接控制这些设备。CPU向磁盘控制器发"读取某个扇区"的指令,然后磁盘控制器自己去操作硬盘,完成数据传输后通过中断通知CPU。这就像一个老板(CPU)给员工(外设)派活——老板发指令,员工去执行,员工干活期间老板可以忙别的事。


三、一行数据库插入操作——CPU和IO的完美分工

回到开头的问题:orderMapper.insert(order)到底是怎么执行的?

这行代码最终会向远在另一台机器上的MySQL服务器发送一条SQL语句,然后等待数据库返回结果。我们把这个过程的每一步拆开,看看CPU在每一步做了什么。

第一步:CPU处理业务逻辑

JVM将orderMapper.insert(order)翻译成一系列机器指令。CPU开始执行这些指令:组织SQL语句、从order对象中提取字段值、拼接成一条INSERT INTO tb_order VALUES (...)字符串。这些操作都是纯计算——CPU在自己的寄存器和缓存里做数据搬运和运算,速度极快,耗时微秒级。

第二步:CPU发起网络IO,然后主动让出CPU

SQL字符串组织好了,CPU现在需要把它发给数据库服务器。Java程序通过JDBC驱动调用底层Socket库,最终CPU执行一条特殊的"OUT"指令,把数据包交给网卡,告诉网卡"把这个包发到目标IP"。

从这一刻起,CPU就不管了。线程向操作系统声明"我在等网络IO,没事干,让其他线程用CPU吧"。操作系统把这个线程标记为"阻塞态",然后让CPU去执行其他线程。

第三步:数据在网络中飞行,CPU在忙别的事

网卡把数据包从电信号转成光信号,通过网线、交换机、路由器,一路传到数据库服务器。数据库服务器的网卡收到数据→交给操作系统→交给MySQL进程→MySQL解析SQL→查找索引→读写磁盘→组织响应→通过网络发回来。

这段时间,你的线程不占用任何CPU时间。它处于"阻塞态",静静地在操作系统内核里等待。CPU被分配给了其他线程——处理另一个用户的请求、执行定时任务、或者干脆空闲等待。

第四步:数据回来了,CPU重新接管

数据库的响应通过网络到达你的服务器。网卡收到数据包,通过硬件中断通知操作系统。操作系统找到那个在等待的线程,把它从"阻塞态"变成"就绪态"——现在它可以被CPU重新调度了。

操作系统再次调度到这个线程时,CPU继续执行之前被暂停下来的指令——把网络数据包里的结果解析出来,封装成Java的返回值对象。插入操作完成。

整个过程的时间分配

假设一次插入操作总共耗时10毫秒:

  • CPU实际工作时间:约0.01毫秒(组织SQL、解析响应)。这部分时间只占总时间的0.1%
  • 线程让出CPU等待网络IO:约9.99毫秒。这部分时间CPU在忙别的事,跟当前线程无关

这就是IO密集型任务的本质——绝大部分时间花在等待IO上,CPU实际处理时间极少。


四、操作系统是怎么调度线程的?

上面提到"操作系统把CPU分配给其他线程",这到底是怎么做到的?

操作系统是唯一的调度者

一个CPU核心在同一时刻只能执行一个线程。但操作系统通过极快速地在多个线程之间切换,让每个线程都感觉自己独占了CPU。

切换过程是这样的:

  1. 当前线程的时间片耗尽或被阻塞。时间片通常是几十毫秒,线程执行完了分配给它的时间段。或者线程自己发起了IO操作,主动进入等待
  2. 操作系统产生一次定时器中断,CPU暂停执行当前线程
  3. 操作系统执行一次"上下文切换":保存当前线程的所有寄存器状态和指令指针(这样下次恢复时能从断点继续执行),然后加载下一个线程的状态
  4. CPU开始执行新线程的指令

线程A的短暂CPU计算→线程A发起IO进入阻塞→操作系统切到线程B→线程B计算→线程B发起IO进入阻塞→操作系统切到线程C…→线程A的IO完成→操作系统把线程A重新放回就绪队列→等到CPU空闲时继续执行它。

这就是"并发"——一个CPU核心在多个线程之间高速切换,让所有线程看起来像在同时运行。

这个切换速度极快——单次上下文切换通常只需要几微秒到几十微秒。在IO密集型场景中,线程大部分时间在等待IO,CPU只在极少的时间段处理这个线程。假设一次数据库操作耗时10ms,CPU实际只处理了0.01ms——剩下的9.99ms内CPU都在为其他线程服务。

这解释了为什么你的8核CPU能轻松处理20个甚至200个消费线程。只要这些线程大多是IO等待,CPU的时间片就不会被任何一个线程长期占用,20个线程的IO等待时间完全可以重叠,吞吐量不会受影响。


五、回到最初的问题——每行代码由谁执行,怎样执行?

现在我们可以完整回答这个问题了。

orderMapper.insert(order)这行代码由谁执行?

由CPU执行。整个计算机里只有CPU能执行指令。你写的Java代码经过JVM翻译成机器指令,然后CPU逐条执行这些指令。

怎样执行?

  1. JVM把Java代码翻译成机器指令
  2. CPU执行这些指令,在极短的时间内组织好SQL语句
  3. CPU把数据包交给网卡,向操作系统声明"我在等IO"。线程进入阻塞态,让出CPU。此时CPU不再执行这个线程的任何指令
  4. 数据在网络中飞行,数据库在处理请求。这段时间CPU在忙别的事——执行其他线程的计算
  5. 网卡收到响应,通过中断通知操作系统。操作系统把等待的线程改为就绪态
  6. CPU再次调度到这个线程,继续执行剩下的指令——解析响应、封装返回值

每行代码都是CPU逐条指令执行的结果。但当代码需要和外部设备交互时,CPU会把耗时的等待留给操作系统和硬件去处理,自己先忙别的事,等数据准备好了再回来继续。


专栏简介

计算机基础——只是想说清楚每行代码是由谁执行的,怎样执行的。

这十二个字是这个专栏的全部内容。不追求面面俱到,只追问最简单的代码背后,从JVM、操作系统到硬件的最核心执行链路。帮助你理解CPU做什么、操作系统做什么、IO设备做什么。把这些基础串成一条线之后,你会发现一切都在这个框架里。同时配合后端技术内核的六个专栏(Java基础、JVM、并发编程、MySQL、Redis、Spring),对代码的理解从"怎么用"贯通到"为什么这么运行"。


这是计算机基础专栏的第一篇,讲清楚了"代码由谁执行、怎样执行"这条核心链路。

http://www.jsqmd.com/news/799285/

相关文章:

  • 结构化技能文档实践指南:从规范到团队知识库构建
  • 告别Jira和Trello?我用ONES的Wiki和测试模块重构了团队协作流程
  • 无线IoT系统硬件级时间同步方案设计与优化
  • LSLib:让《神界原罪》和《博德之门3》MOD制作变得高效完整的实用指南
  • niri下的窗口透明问题(wezterm, kitty)
  • AI- RAG笔记02 - Load Chunking
  • 弹性关节四足机器人冲击缓冲与能耗优化【附仿真】
  • 别让单位设置坑了你!Cadence Allegro出Gerber的英制/公制选择避坑指南
  • 嵌入式实时数据显示系统:从架构设计到ESP32实战
  • 我把 K8s 发布事故率从 30% 降到 0,只用对了这 3 个配置
  • 怎么找到你的第一个 good first issue:新手选题比写代码更重要
  • 告别手动出图!用ArcMap数据驱动页面,5分钟搞定乡镇影像图批量导出PDF
  • AI编程助手技能包:samber/cc-skills提升Claude与Cursor专业输出
  • 构建极简代码片段管理器:从命令行工具到开发效率提升
  • linux学习进展 I/O复用函数——epoll详解(ET,IT模式)
  • 市场营销Agent:自动生成内容与投放策略
  • 从零开始学AI:一个面向新手的终极学习指南
  • AWD平台搭建后别忘了这几步:从计分板查看、SSH连接到Flag提交的完整使用手册
  • JPEXS Free Flash Decompiler:Flash逆向工程与SWF反编译的终极解决方案
  • 微信小程序云开发环境搭建与REST API混合架构实战
  • AY Claude CLI:Claude生态的标准化包管理工具
  • 从暗房到云端:Red Cabbage印相技术溯源(1842年赫歇尔氰版工艺 × MJ v6.3神经渲染架构对比白皮书)
  • SteamAutoCrack终极指南:3步实现Steam游戏自动化破解与DRM移除
  • 【网络排查指南】IDEA连接MySQL报错08S01:从“0毫秒”到稳定连接的深度修复
  • 最新发布|2026年5月企业商旅平台排行实力全解析+避坑指南
  • Agentfiles:统一管理AI编码助手技能文件的Obsidian插件
  • 横向评测:东莞主流AI培训课程关键维度对比
  • Micronaut应用瘦身利器:静态分析与死代码消除实战
  • linux学习进展 libevent
  • [ STK 与 Matlab 联动 ] 构建动态卫星可见性矩阵:从数据获取到批量处理实战