禅与计算机 禅与计算机
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

sharkchili

计算机禅修者
首页
  • Java基础

    • 聊一聊java一些核心知识点
    • 聊聊java面向对象核心知识点
    • 聊聊Java中的异常
    • 聊聊Java中的常用类String
    • 万字长文带你细聊Java注解本质
    • 来聊聊Java的反射机制
    • 深入解析Java泛型的魅力与机制
    • Java集合框架深度解析与面试指南
    • Java常用集合类HashMap深度解析
    • LinkedHashMap源码到面试题的全解析
    • 深入解析CopyOnWriteArrayList的工作机制
    • Java基础IO总结
    • Java三大IO模型小结
    • Java BIO NIO AIO详解
    • Java进阶NIO之IO多路复用详解
    • Java8流式编程入门
    • 一文速通lambda与函数式编程
    • Java8函数式方法引用最佳实践
  • Java并发编程

    • Java并发编程基础小结
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 详解JUC包下的锁
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • Java并发容器总结
    • 详解Java并发编程volatile关键字
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步多任务最佳实践
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 来聊一个有趣的限流器RateLimiter
  • JVM相关

    • 从零开始掌握 JVM
    • JVM核心知识点小结
    • JVM指令集概览:基础与应用
    • JVM类加载器深度解析
    • JVM方法区深度解析
    • Java内存模型JMM详解
    • Java对象大小的精确计算方法
    • 逃逸分析在Java中的应用与优化
    • 从零开始理解JVM的JIT编译机制
    • G1垃圾回收器:原理详解与调优指南
    • JVM故障排查实战指南
    • JVM内存问题排错最佳实践
    • JVM内存溢出排查指南
    • 简明的Arthas使用教程
    • 简明的Arthas配置及基础运维教程
    • 基于Arthas Idea的JVM故障排查与指令生成
    • 基于arthas量化监控诊断java应用方法论与实践
    • 深入剖析arthas技术原理
  • 深入理解Spring框架

    • Spring 核心知识点全面解析
    • Spring核心功能IOC详解
    • Spring AOP 深度剖析与实践
    • Spring 三级缓存机制深度解析
    • 深入 Spring 源码,剖析设计模式的落地实践
    • 探索 Spring 事务的奥秘
    • 深入解析Spring Bean的生命周期管理
    • 解读 Spring Boot 核心知识点
    • Spring Boot 启动优化实战:1分钟到13秒的排查与优化之路
    • Spring Boot自动装配原理及实践
    • 一文快速上手Sharding-JDBC
    • sharding-jdbc如何实现分页查询
    • 基于DynamicDataSource整合分库分表框架Shardingsphere
  • 计算机组成原理

    • 计算机硬件知识小结
    • CPU核心知识点小结
    • 浅谈CPU流水线的艺术
    • 从Java程序员视角聊聊CPU缓存
    • CPU任务调度和伪共享问题小结
    • CPU MESI缓存一致性协议
    • CPU内存管理机制
    • 内存深度解析
    • 磁盘存储原理
    • 详解计算机启动步骤
    • CPU南北桥架构与发展史
    • CPU中断机制与硬件交互详解
  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
  • 计算机网络

    • 网卡通信原理详解
    • 网卡数据包处理指南
    • 基于抓包详解TCP协议
  • 编码最佳实践

    • 浅谈现代软件工程TDD最佳实践
    • 浅谈TDD模式下并发程序设计与实现
    • 面向AI编程新范式Trae后端开发环境搭建与实践
    • 基于提示词工程的Redis签到功能开发实践
    • 基于Vibe Coding的Redis分页查询实现
    • 告别AI无效对话:资深工程师的提示词设计最佳实践
  • 实用技巧与配置

    • Mac常用快捷键与效率插件指南
    • Keynote技术科普短视频制作全攻略
  • 写作

    • 写好技术博客的5大核心原则:从认知科学到AI工具的全流程指南
  • 开发工具

    • IDEA配置详解与高效使用指南
  • Nodejs
  • 博客搭建
  • Redis

    • Redis核心知识小结
    • 解锁Redis发布订阅模式
    • 掌握Redis事务
    • Redis主从复制技术
    • Redis的哨兵模式详解
    • 深度剖析Redisson分布式锁
    • 详解redis单线程设计思路
    • 来聊聊Redis所实现的Reactor模型
    • Redis RDB持久化源码深度解析
    • 来聊聊redis的AOF写入
    • 来聊聊Redis持久化AOF管道通信的设计
    • 来聊聊redis集群数据迁移
    • Redis SDS动态字符串深度解析
    • 高效索引的秘密:redis跳表设计与实现
    • 聊聊redis中的字典设计与实现
  • MySQL

    • MySQL基础知识点小结
    • 解读MySQL 索引基础
    • MySQL 索引进阶指南
    • 解读MySQL Explain关键字
    • 探秘 MySQL 锁:原理与实践
    • 详解MySQL重做日志redolog
    • 详解undoLog在MySQL MVCC中的运用
    • MySQL二进制日志binlog核心知识点
    • MySQL高效插入数据的最佳实践
    • MySQL分页查询优化指南
    • MySQL流式查询的奥秘与应用解析
    • 来聊聊分库分表
    • 来聊聊大厂常用的分布式ID生成方案
  • ElasticSearch

    • 从Lucene到Elasticsearch:进化之路
    • ES 基础使用指南
    • ElasticSearch如何写入一篇文档
    • 深入剖析Elasticsearch文档读取原理
    • 聊聊ElasticSearch性能调优
    • Spring借助Easy-Es操作ES
  • Netty

    • 一文快速了解高性能网络通信框架Netty
    • Netty网络传输简记
    • 来聊聊Netty的ByteBuf
    • 来聊聊Netty消息发送的那些事
    • 解密Netty高性能之谜:NioEventLoop线程池阻塞分析
    • 详解Netty中的责任链Pipeline如何管理ChannelHandler
    • Netty Reactor模型常见知识点小结
    • Netty如何驾驭TCP流式传输?粘包拆包问题全解
    • Netty解码器源码解析
  • 消息队列

    • 一文快速入门消息队列
    • 消息队列RocketMQ入门指南
    • 基于RocketMQ实现分布式事务
    • RocketMQ容器化最佳实践
    • RocketMQ常见问题与深度解析
    • Kafka快速安装与使用指南
  • Nginx

    • Linux下的nginx安装
    • Nginx基础入门总结
    • Nginx核心指令小结
    • Nginx进程结构与核心模块初探
    • Nginx应用进阶HTTP核心模块配置
    • Nginx缓存及HTTPS配置小记
    • nginx高可用实践简记
    • Nginx性能优化
  • 微服务基础

    • 微服务基础知识小结
    • 分布式事务核心概念小结
    • OpenFeign核心知识小结
    • 微服务组件Gateway核心使用小结
    • 分布式事务Seata实践
    • 用 Docker Compose 完成 Seata 的整合部署
  • Nacos

    • Nacos服务注册原理全解析
    • Nacos服务订阅流程全解析
    • Nacos服务变更推送流程全解析
    • 深入解析SpringCloud负载均衡器Loadbalancer
    • Nacos源码环境搭建与调试指南
  • Seata

    • 深度剖析Seata源码
  • Docker部署

    • 一文快速掌握docker的理念和基本使用
    • 使用docker编排容器
    • 基于docker-compose部署微服务基本环境
    • 基于docker容器化部署微服务
    • Gateway全局异常处理及请求响应监控
    • Docker图形化界面工具Portainer最佳实践
  • Go基础

    • 一文带你速通Go语言基础语法
    • 一文快速掌握Go语言切片
    • 来聊聊go语言的hashMap
    • 一文速通go语言类型系统
    • 浅谈Go语言中的面向对象
    • go语言是如何实现协程的
    • 聊聊go语言中的GMP模型
    • 极简的go语言channel入门
    • 聊聊go语言基于epoll的网络并发实现
    • 写给Java开发的Go语言协程实践
  • mini-redis实战

    • 来聊聊我用go手写redis这件事
    • mini-redis如何解析处理客户端请求
    • 实现mini-redis字符串操作
    • 硬核复刻redis底层双向链表核心实现
    • 动手复刻redis之go语言下的字典的设计与落地
    • Go 语言下的 Redis 跳表设计与实现
    • Go 语言版 Redis 有序集合指令复刻探索
  • 项目编排

    • Spring脚手架创建简记
    • Spring脚手架集成分页插件
    • Spring脚手架集成校验框架
    • maven父子模块两种搭建方式简记
    • SpringBoot+Vue3前后端快速整合入门
    • 来聊聊Java项目分层规范
  • 场景设计

    • Java实现文件分片上传
    • 基于时间缓存优化浏览器轮询阻塞问题
    • 基于EasyExcel实现高效导出
    • 10亿数据高效插入MySQL最佳方案
    • 从开源框架中学习那些实用的位运算技巧
  • CI/CD

    • 基于NETAPP实现内网穿透
    • 基于Gitee实现Jenkins自动化部署SpringBoot项目
    • Jenkins离线安装部署教程简记
    • 基于Nexus搭建Maven私服基础入门
    • 基于内网的Jenkins整合gitlab综合方案简记
  • 监控方法论

    • SpringBoot集成Prometheus与Grafana监控
    • Java监控度量Micrometer全解析
    • 从 micrometer计量器角度快速上手promQL
    • 硬核安利一个监控告警开源项目Nightingale
  • Spring AI

    • Spring AI Alibaba深度实战:一文掌握智能体开发全流程
    • Spring AI Alibaba实战:JVM监控诊断Arthas Agent的工程化构建与最佳实践
  • 大模型评测

    • M2.7 真能打!我用两个真实场景测了测,结果有点意外
    • Qoder JetBrains插件评测:祖传代码重构与接口优化实战
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 计算机组成原理

  • 操作系统

    • 如何实现一个高性能服务器
    • Linux文件结构与文件权限
    • Linux常见压缩指令小结
    • 浅谈Linux权限管理
    • Linux核心系统调用详解
    • Linux进程管理
    • Linux线程管理
    • 进程与线程深度解析
    • Linux进程间通信机制
    • 浅谈Linux基于信号处理中断的哲学
    • 从操作系统底层浅谈程序栈的高效性
    • 零拷贝技术原理与实践
    • CPU缓存一致性问题深度解析
    • IO任务与CPU调度艺术
      • 引言
      • 详解操作系统对于线程的调度
        • 操作系统的任务调度
        • IO阻塞的线程会如何避免CPU资源占用
        • 用一个实例了解网络收包的过程
      • CPU如何处理任务优先级分配
      • 详解Java中的阻塞方法Thread.sleep()
        • Thread.sleep()如何优化抢占式调度的饥饿问题
        • RocketMQ中关于Thread.sleep(0)的经典案例
      • 小结
      • 参考
    • 来聊聊函数回调
    • 一个完美主义者的自我救赎
  • 计算机网络

  • 运维

  • 编码最佳实践

  • 计算机基础
  • 操作系统
sharkchili
2026-03-25
目录

IO任务与CPU调度艺术

[toc]

# 引言

近期和同行谈及一些操作系统下关于性能指标评估的话题,涉及一些计算机基础的核心知识点,遂以此文针对如下几个话题进行深入分析:

  1. 为什么并发的IO任务使用多线程效率更高?
  2. CPU在任务IO阻塞时发生了什么?
  3. CPU切换线程的依据是什么?
  4. 线程休眠有什么用?
  5. 线程休眠1秒后是否会立刻拿到CPU执行权。
  6. 为什么有人代码会用到Thread.sleep(0);它的作用是什么?

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# 详解操作系统对于线程的调度

# 操作系统的任务调度

近代CPU运算速度是非常快的,即使在多线程情况下CPU会按照操作系统的调度算法有序快速有序的执行任务,使得我们即使开个十几个进程,肉眼上所有的进程几乎的是并行运行的,这本质上就是CPU对应ns级别的线程任务调度切换以及人眼200ms下不会直观感受到停顿的共同作用:

CPU执行线程时会按照任务优先级进行处理,一般而言,对于硬件产生的信号优先级都是最高的,当收到中断信号时,CPU理应中断手头的任务去处理硬件中断程序。例如:用户键盘打字输入、收取网络数据包。

以用户键盘打字输入为例,从键盘输入到CPU处理的流程为:

  1. 用户在键盘键入一个按键指令
  2. 键盘给CPU发送一个中断引脚发送一个高电平。
  3. CPU执行键盘的中断程序,获取键盘的数据。

同理,获取网络数据包的执行流程为:

  1. 网卡收到网线传输的网络数据
  2. 通过硬件电路完成数据传输
  3. 将数据写入到内存中的某个地址中
  4. 网卡发送一个中断信号给CPU
  5. CPU响应网卡中断程序,从内存中读取数据

了解网络数据包获取流程整个流程后,不知道读者是否发现,网卡读取数据期间CPU似乎无需参与工作的,那么操作系统是如何处理这期间的任务调度呢?

# IO阻塞的线程会如何避免CPU资源占用

操作系统为了支持多任务,将任务分为了运行、等待、就绪等几种状态,对于运行状态的任务,操作系统会将其放到工作队列中。CPU按照操作系统的调度算法按需执行工作队列中的任务。

需要注意的是,这些任务能够被CPU时间片完整执行的前提是任务不会发生阻塞。一旦任务或是读取本地文件或者发起网络IO等原因发起阻塞,这些线程任务就会被放到等待队列中,就下图所有的收取网络数据包,在网卡读取数据并写入到内存这期间,该任务就是在等待队列中完成的。 只有这些IO任务接受到了完整的数据并通过中断程序发送信号给CPU,操作系统才会将其放到工作队列中,让CPU读取数据。

这也就是IO阻塞避免CPU资源消耗的原因,即在IO阻塞态时,CPU会将这些任务挂起切换执行其它任务,等其IO数据准备就绪并发起中断信号时,再回头处理这些任务。

# 用一个实例了解网络收包的过程

对于上述问题,我们不妨看一段这样的代码,功能很简单,服务端开启9009端口获取客户端输入的信息。

服务端代码如下,逻辑也很清晰,执行步骤为:

  1. 创建ServerSocket 服务器。
  2. 绑定端口。
  3. 阻塞监听等待客户端连接。
  4. 处理客户端发送的数据。
  5. 回复数据给客户端。
public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = null;

        try {
            // 创建服务器 Socket 并绑定 9009 端口
            serverSocket = new ServerSocket(9009);
        } catch (IOException e) {
            System.err.println("Could not listen on port: 9009.");
            System.exit(1);
        }

        Socket clientSocket = null;
        System.out.println("Waiting for connection...");

        try {
            // 等待客户端连接
            clientSocket = serverSocket.accept();
            System.out.println("Connection successful!");
        } catch (IOException e) {
            System.err.println("Accept failed.");
            System.exit(1);
        }

        //输出流
        PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);

        //输入流
        BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

        String inputLine;

        while ((inputLine = in.readLine()) != null) { // 不断读取客户端发送的消息
            System.out.println("Client: " + inputLine);
            out.println("Server: Welcome to the server!"); // 向客户端发送欢迎消息
        }

        out.close();
        in.close();
        clientSocket.close();
        serverSocket.close();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

客户端代码示例如下,执行步骤为:

  1. 连接服务端。
  2. 输入要发送的数据。
  3. 发送数据。
  4. 获取响应。
public class Client {
    public static void main(String[] args) throws IOException {
        Socket socket = null;
        PrintWriter out = null;
        BufferedReader in = null;

        try {
            socket = new Socket("localhost", 8080); // 连接到服务器
            out = new PrintWriter(socket.getOutputStream(), true);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        } catch (UnknownHostException e) {
            System.err.println("Unknown host: localhost.");
            System.exit(1);
        } catch (IOException e) {
            System.err.println("Couldn't get I/O for the connection to: localhost.");
            System.exit(1);
        }

        BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
        String userInput;

        while ((userInput = stdIn.readLine()) != null) { // 不断从控制台读取用户输入
            out.println(userInput); // 向服务器发送消息
            System.out.println("Server: " + in.readLine()); // 从服务器读取消息并打印到控制台
        }

        out.close();
        in.close();
        stdIn.close();
        socket.close();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

启动服务端,我们会看到这样一段输出:

Waiting for connection...
1

并通过客户端发送字符串hello world,服务端的输出结果如下:

Waiting for connection...
Connection successful!
Client: hello world
1
2
3

了解整个流程之后,我们再对细节进行分析。对于服务端的每一个步骤,CPU对应做法如下:

  1. new ServerSocket(9009) 新建由文件系统管理的Socket对象,并绑定9009端口。

  2. serverSocket.accept();阻塞监听等待客户端连接,此时CPU就会将其放到等待队列中,去处理其他线程任务。

  1. 客户端发起连接后,服务端网卡收到客户端请求连接,通过中断程序发出信号,CPU收到中断信号后挂起当前执行的线程去响应连接请求。
  2. 服务端建立连接成功,输出Connection successful!
  3. in.readLine()阻塞获取用户发送数据,CPU再次将其放到等待队列中,处理其他非阻塞的线程任务。
  4. 客户端发送数据,网卡接收并将其存放到内存中,通过中断程序发出信号,CPU收到中断信号后挂起当前执行的线程去读取响应数据。
  5. 重复5、6两步。

# CPU如何处理任务优先级分配

上文我们提到过CPU会按照某种调度算法执行进程任务,这里的算法大致分为两种:

  1. 抢占式
  2. 非抢占式

先来说说抢占式算法,典型实现就是Windows系统,它会在调度前计算每一个线程的优先级,然后按照优先级执行任务,执行任务直到执行到线程主动挂起释放执行权或者CPU察觉到该线程霸占CPU执行时间过长将其强行挂起。 此后会再次重新计算一次优先级,在这期间,那些等待很久的线程优先级就会被大大提高,然后CPU再次找出优先级最高的线程任务执行。 之所以我们称这种算法为抢占式,是因为每次进行重新分配时不一定是公平的。假设线程1第一次执行到期后,CPU重新计算优先级,结果发现还是线程1优先级最高,那么线程1依然会再次获得CPU执行权,这就导致其他线程一直没有执行的机会,极可能出现线程饥饿的情况。

Unix操作系统用的就是非抢占式调度算法,即时间分片算法,它会将时间平均切片,每一个进程都会得到一个平均的执行时间,只有任务执行完分片算法分配的时间或者在执行期间发生阻塞,CPU才会切换到下一个线程执行。因为时间分片是平均的,所以分片算法可以保证尽可能的公平。

# 详解Java中的阻塞方法Thread.sleep()

# Thread.sleep()如何优化抢占式调度的饥饿问题

上文提到抢占式算法可能导致线程饥饿的问题,所以我们是否有什么办法让长时间霸占CPU的线程主动让CPU重新计算一次优先级呢? 答案就是Thread.sleep()方法,通过该方法就相当于对当前线程任务的一次洗牌,它会让当前线程休眠进入等待队列,此时CPU就会重新计算任务优先级。这样一来那些因为长时间等待使得优先级被拔高的线程就会被CPU优先处理了:

# RocketMQ中关于Thread.sleep(0)的经典案例

对应代码如下可以看到在RocketMQ这个大循环中,处理一些刷盘的操作,该因为是大循环,且涉及数据来回传输等操作,所以循环期间势必会创建大量的垃圾对象。

所以代码中有个if判断调用了Thread.sleep(0),作用如上所说,假设运行Java程序的操作系统采用抢占式调度算法,可能会出现以下流程:

  1. 大循环长时间霸占CPU导致处理GC任务的线程迟迟无法工作。
  2. 循环结束后堆内存中出现大量因为刷盘等业务操作留下的垃圾对象。
  3. 等待长时间后,操作系统重新进行一次CPU竞争,假设此时等待已久的处理GC任务的线程优先级最高,于是执行权分配给了GC线程。
  4. 因为堆内存垃圾太多,导致长时间的GC。

所以设计者们考虑到这一点,这在循环内部每一个小节点时调用Thread.sleep(),确保每执行一小段时间执行让操作系统进行一次CPU竞争,让GC线程尽可能多执行,做到垃圾回收的削峰填谷,避免后续出现一次长时间的GC时间导致STW进而阻塞业务线程的运行。

for (int i = 0, j = 0; i < this.fileSize; i += MappedFile.OS_PAGE_SIZE, j++) {
        byteBuffer.put(i, (byte) 0);
        // force flush when flush disk type is sync
        if (type == FlushDiskType.SYNC_FLUSH) {
            if ((i / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE) >= pages) {
                flush = i;
                mappedByteBuffer.force();
            }
        }

        // prevent gc
        if (j % 1000 == 0) {
            log.info("j={}, costTime={}", j, System.currentTimeMillis() - time);
            time = System.currentTimeMillis();
            try {
                Thread.sleep(0);
            } catch (InterruptedException e) {
                log.error("Interrupted", e);
            }
        }
 }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

那为什么设计者们不使用Thread.sleep()而是调用Thread.sleep(0)方法呢?原因如下:

  1. 调用sleep方法仅仅是为了让操作系统重新进行一次CPU竞争,并不是为了挂起当前线程。
  2. 并不是每次sleep都需要垃圾回收,设置为0可以确保当前大循环的线程让出CPU执行权并休眠0s,即一让出CPU时间片就参与CPU下一次执行权的竞争。

不得不说RocketMQ的设计们对于编码的功力是非常深厚的。

# 小结

到此为止,我们了解的操作系统对于CPU执行线程任务的调度流程,回到我们文章开头提出的几个问题:

1. 为什么并发的IO任务使用多线程效率更高?
答:IO阻塞的任务会让出CPU时间片,自行处理IO请求,确保操作系统尽可能榨取CPU利用率。

2. CPU在任务IO阻塞时发生了什么?
答:将任务放入等待队列,并切换到下一个要执行的线程中。

3. CPU切换线程的依据是什么?
答:有可能是分配给线程的时间片到期了,有可能是因为线程阻塞,还有可能因为线程霸占CPU太久了(针对抢占式算法)

4. 线程休眠有什么用?
答:以抢占式算法为例,线程休眠会将当前任务存入等待队列,并让CPU重新计算任务优先级,选出当前最高优先级的任务。

5. 线程休眠1秒后是否会立刻拿到CPU执行权。
答:不一定,CPU会按照调度算法执行任务,这个不能一概而论。

6. 为什么有人代码会用到`Thread.sleep(0);`它的作用是什么?
答:让当前线程让出CPU执行权,所有线程重新进行一次CPU竞争,优先级高的获取CPU执行权。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。

为方便与读者交流,现已创建读者群。关注上方公众号获取我的联系方式,添加时备注加群即可加入。

# 参考

操作系统面试题:进程如何阻塞?进程阻塞为什么不占用CPU?:https://blog.csdn.net/weixin_44844089/article/details/115655642 (opens new window)

Thread.sleep(0)并不是写错了,而是有妙用!:https://mp.weixin.qq.com/s/Zt8gnddY1fxFJpWT8mhWvA (opens new window)

面试题-Thread.sleep(0)的作用是什么:https://www.cnblogs.com/east7/p/14502400.html (opens new window)

没有二十年功力,写不出Thread.sleep(0)这一行“看似无用”的代码!:https://segmentfault.com/a/1190000042432589 (opens new window)

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
CPU缓存一致性问题深度解析
来聊聊函数回调

← CPU缓存一致性问题深度解析 来聊聊函数回调→

最近更新
01
基于EasyExcel实现高效导出
03-25
02
从开源框架中学习那些实用的位运算技巧
03-25
03
浅谈分布式架构设计思想和常见优化手段
03-25
更多文章>
Theme by Vdoing | Copyright © 2025-2026 Evan Xu | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×
×