禅与计算机 禅与计算机
首页
  • 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)
  • Java基础

  • 并发编程

    • Java并发编程基础小结
      • 写在文章开头
      • 并发编程中的一些核心思想
        • 为什么需要多线程
        • 多线程有哪些优势
        • 并发编程需要关注的问题
        • 安全性问题
        • 活跃性问题
        • 性能问题
      • JVM视角下的进程和线程
      • 多线程常见问题
        • 程序计数器、虚拟机栈、本地方法栈为什么线程中是各自独立的
        • 并发和并行的区别是什么?
        • 同步和异步是什么意思?
        • 为什么需要多线程,多线程解决了什么问题
        • 创建线程方式有哪些
        • 为什么需要Runnable接口实现多线程
        • Thread和Runnable使用的区别
        • Thread类中run()和start()的区别
        • Java线程有哪几种状态
        • 和操作系统的线程状态的区别
        • 什么是上下文切换
        • 线程死锁问题
        • sleep和wait方法区别
        • 为什么sleep会定义在Thread
        • 为什么wait会定义在Object 上
        • 可以直接调用 Thread 类的 run 方法吗?
        • 假如在进程中, 已经开辟了多个线程, 其中一个线程怎么中断其它线程?
        • IO阻塞的线程会占用CPU资源吗?如何避免线程霸占CPU?
      • 小结
      • 参考
    • 深入理解Java中的final关键字
    • 浅谈Java并发安全发布技术
    • 浅谈Java并发编程中断的哲学
    • 浅谈传统并发编程的优化思路
    • Java线程池知识点小结
    • 浅谈Java线程池中拒绝策略与流控的艺术
    • 浅谈池化技术的优雅关闭
    • 浅谈守护线程与进程优雅关闭
    • 浅谈并发编程等待通知模型
    • synchronized关键字使用指南
    • 深入源码解析synchronized关键字
    • 深入理解synchronized同步机制
    • 详解JUC包下的锁
    • 详解JUC包下各种锁的使用
    • 详解并发编程中的CAS原子类
    • LongAdder源码分析
    • AQS源码解析
    • 深入剖析Java并发编程中的死锁问题
    • 详解Java并发流程控制工具
    • Java并发容器总结
    • 深入解析CopyOnWriteArrayList
    • 详解Java并发编程volatile关键字
    • 聊聊JVM中安全点的概念
    • 并发编程ThreadLocal必知必会
    • CompletableFuture基础实践小结
    • CompletableFuture异步IO密集型任务最佳实践
    • CompletableFuture异步多任务最佳实践
    • CompletableFuture组合流水线任务实践
    • 实现一个简单实用的的并发同步模型
    • 解决Java并发问题的常见思路
    • 来聊一个有趣的限流器RateLimiter
    • 硬核详解FutureTask设计与实现
    • 线程池大小设置的底层逻辑与场景化方案
    • 记一个ConcurrentHashMap使用不当导致的并发事故
    • JS设计模式总结笔记
    • 小程序笔记
    • 《JavaScript教程》笔记
  • JVM相关

  • 深入理解Spring框架

  • Java核心技术
  • 并发编程
sharkchili
2026-03-25
目录

Java并发编程基础小结

[toc]

# 写在文章开头

这也算是笔者一直重构梳理的一篇文章,不同的阶段对于并发编程的禅修都有不一样的理解,而本次的进阶将更多维度是去强调并发编程所需要关注的一些基础问题和本质,希望对你有帮助。 我是 SharkChili ,Java 开发者,Java Guide 开源项目维护者。欢迎关注我的公众号:写代码的SharkChili,也欢迎您了解我的开源项目 mini-redis:https://github.com/shark-ctrl/mini-redis (opens new window)。

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

# 并发编程中的一些核心思想

# 为什么需要多线程

计算机发展初期都是以进程为维度分配内存、文件句柄以及安全证书等资源,同时多个进程之间采用一些比较粗粒度的通信机制来交换数据,包括:

  1. 套接字
  2. 信号处理器
  3. 共享内存

基于并发编程实战的思想:

高效做事的人,总能在串行性和异步性之间找到一个合理的平衡点,程序也是如此。

于是操作系统就引入多进程运行的调度机制,例如下面这个步骤:

  1. 在一个单核的计算机上进程1得到CPU执行权,随后进入IO读取任务阻塞挂起
  2. 处于操作系统就绪队列的进程2被唤醒得到CPU时间片执行任务
  3. 进程1在IO读取完成后收到中断响应也随后进入就绪队列,等待CPU执行权

基于上述基础上,考虑到每一个进程都独有各自的内存空间和文件句柄等资源,以如此庞大级别的单位处理一些单一的工作而在CPU之间进行频繁切换开销是非常不客观的,于是就有了轻量级调度单位——多线程。

以多线程调度为例,假设进程1、进程2分别对应读取定时读取网络数据、定时写入数据到网络系统日志,按照多线程维度将二者合并,最终的进程交由CPU执行,我们就可以得到这样一个场景:

  1. CPU执行到线程1,读取网络数据,IO阻塞,让出CPU。
  2. 线程2写入之前的网络系统日志到磁盘,进行write调用时切换到内核态,让出CPU。
  3. 线程1完成数据,进程终端输出结果,让出CPU。
  4. 线程2write调用返回,继续进行下一次的写入......

# 多线程有哪些优势

如上面所说,多线程存在如下优势:

  1. 轻量:以线程为单位构成进程,共享进程范围内的资源,例如内存、文件句柄等。
  2. 返回多核处理器的强大能力:操作系统以更轻量级的线程为单位进行高效的调度和切换,在设计合理的情况下,可以大大提升CPU的利用率。
  3. 建模简单性:利用多线程技术,可以将复杂的异步任务组合的同步工作流(例如JDK8中的CompleteFuture工具类),并利用多线程分别执行这些任务,在指定时机进行同步交互。
  4. 异步事件简化处理:有了多线程的概念之后,早期尝试过用BIO技术即一个线程分配一个客户端socket,好在现代Unix系统提出epoll、io_uring的良好设计,使得多线程技术有了更好的发挥。

# 并发编程需要关注的问题

# 安全性问题

首先是线程安全性问题,因为多线程共享了一块进程的数据,如果没有充分的做好线程间的同步,就会出现一些意外的情况,就例如下面这段代码,多线程操作一个num,因为自增操作非复合操作且多线程操作彼此不可见,出现意外结果:

private static int num = 0;

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(() -> {
            for (int i = 0; i < 100_0000; i++) {
                num++;
            }
            countDownLatch.countDown();
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 100_0000; i++) {
                num++;
            }
            countDownLatch.countDown();
        }).start();

        countDownLatch.await();
        System.out.println(num);//输出1499633

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

同样的,如果没有良好的同步机制,编译器、处理器都可以针对指令进行任意顺序和时间执行,同时在处理器或者寄存器缓存线程变量的情况下的修改操作,其他处理器的线程是无法看到其修改操作,也会导致逻辑运算上的错乱:

# 活跃性问题

线程活跃性问题即线程未能按照预期的时许执行,导致线程持续的活跃最典型的表现就是无限循环,打满CPU。例如并发环境下两个CPU分别执行线程0和线程1的逻辑,即:

  1. 线程0执行无限循环,只要val变为true则终止无限循环,
  2. 线程1休眠一段时间后将val修改为true。

对于java并发编程而言,如果没有添加保证可见性的关键字进行修饰,线程1的修改操作对于线程0来说是不可见的,此时就会出现下图所示的线程1修改仅对自己可见,并不会即使刷新到CPU多核共享内存L3 Cache,进而导致线程0无限循环,也就是我们所说的活跃性问题:

对应我们也可以出示例代码,同时笔者也会在后续的文章中来补充说明这一点的解决方案:

	private static  boolean val = false;

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(() -> {
            while (!val) {//下方线程操作对于线程1不可见,进行无限循环

            }
            System.out.println("thread-1 executed finished");
            countDownLatch.countDown();
        }).start();


        new Thread(() -> {
            ThreadUtil.sleep(5, TimeUnit.SECONDS);
            val = true;
            System.out.println("设置val为true");
            countDownLatch.countDown();

        }).start();

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
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

通常来说活跃性问题都是由以下几种错误导致:

  1. 死锁:即两个线程互相等待对象持有的资源进入阻塞
  2. 活锁:上述的活跃性问题就是最经典的活锁
  3. 线程饥饿:因为线程过多或者某些原因导致某个线程长时间未能分配到CPU时间片,导致任务迟迟无法结束,这就是典型的线程饥饿问题

# 性能问题

这一点是老生常态了,应对并发安全的手段就是保证可见性和互斥,这涉及CPU缓存更新和临界资源维度的把控和并发运算技巧,一般来说导致多线程性能瓶颈的几种原因可分为:

  1. 同步机制抑制了某些编译器的优化,例如synchronized关键字。
  2. 共享变量在多处理器之间不同线程执行,线程切换时处理器的缓存数据局部性失效,使得开销大部分时间都在处理线程调度而非运算,这也会导致程序的执行性能下降。
  3. 多线程并发处理时切换线程时产生保存和恢复上下文的开销。

# JVM视角下的进程和线程

如下图所示,可以看出线程是比进程更小的单位,进程是独立的,彼此之间不会干扰,但是线程在同一个进程中共享堆区和方法区,虽然开销较小,但是资源之间管理和分配处理相对于进程之间要更加小心。

# 多线程常见问题

# 程序计数器、虚拟机栈、本地方法栈为什么线程中是各自独立的

  1. 程序计数器私有的原因:学过计算机组成原理的小伙伴应该都知晓,程序计数器用于记录当前下一条要执行的指令的单元地址,JVM也一样,有了程序计数器才能保证在多线程的情况下,这个线程被挂起再被恢复时,我们可以根据程序计数器找到下一次要执行的指令的位置。
  2. 虚拟机栈私有的原因:每一个Java线程在执行方法时,都会创建一个栈帧用于保存局部变量、常量池引用、操作数栈等信息,在这个方法调用到完成前,它对应的信息都会基于栈帧保存在虚拟机栈上。
  3. 本地方法栈私有的原因:和虚拟机栈类似,只不过本地方法栈保存的native方法的信息。

所以为了保证局部变量不被别的线程访问到,虚拟机栈和本地方法栈都是私有的,这就是我们解决某些线程安全问题时,常会用到一个叫栈封闭技术。

关于栈封闭技术如下所示,将变量放在局部,每个线程都有自己的虚拟机栈,线程安全

public class StackConfinement implements Runnable {

    //全部变量 多线操作会有现场问题
    int globalVariable = 0;

    public void inThread() {
        //栈封闭技术,将变量放在局部,每个线程都有自己的虚拟机栈 线程安全
        int neverGoOut = 0;
        synchronized (this) {
            for (int i = 0; i < 10000; i++) {
                neverGoOut++;
            }
        }

        System.out.println("栈内保护的数字是线程安全的:" + neverGoOut);//栈内保护的数字是线程安全的:10000

    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            globalVariable++;
        }
        inThread();
    }

    public static void main(String[] args) throws InterruptedException {
        StackConfinement r1 = new StackConfinement();
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r1);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        
        System.out.println(r1.globalVariable); //13257
    }
}
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

# 并发和并行的区别是什么?

  1. 并发:并发我们可以理解为,两个线程先后执行,但是从宏观角度来看,他们几乎是并行的。
  2. 并行:并行我们可以理解为两个线程同一时间都在运行。

# 同步和异步是什么意思?

  1. 同步:同步就是一个调用没有结果前,不会返回,直到有结果的才返回。
  2. 异步:异步即发起一个调用后,不等结果如何直接返回。

# 为什么需要多线程,多线程解决了什么问题

从宏观角度来看:线程可以理解为轻量级进程,切换开销远远小于进程,所以在多核CPU的计算机下,使用多线程可以更好的利用计算机资源从而提高计算机利用率和效率来应对现如今的高并发网络环境。

从微观场景下来说: 单核场景,在单核CPU情况下,假如一个线程需要进行IO才能执行业务逻辑,若只有单线程,这就意味着IO期间发生阻塞线程却只能干等。假如我们使用多线程的话,在当前线程IO期间,我们可以将其挂起,让出CPU时间片让其他线程工作。

多核场景下,假如我们有一个很复杂的任务需要进程各种IO和业务计算,假如只有一个线程的话,无论我们有多少个CPU核心,因为单线程的缘故他永远只能利用一个CPU核心,假如我们使用多线程,那么这些线程就会映射到不同的CPU核心上,做到最好的利用计算机资源,提高执行效率,执行事件约为单线程执行事件/CPU核心数。

# 创建线程方式有哪些

直接继承Thread启动运行:

public static void main(String[] args) {
        new Task().start();
    }

    /**
     * 继承thread重写run方法
     */
    private static class Task extends Thread {
        @Override
        public void run() {
            Console.log("{} is running", Thread.currentThread().getName());
        }
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14

通过继承Runable实现run方法并提交给thread运行:

public static void main(String[] args) {
       new Thread(new Task()).start();
    }

    /**
     * 继承Runnable重写run方法
     */
    private static class Task implements Runnable {
        @Override
        public void run() {
            Console.log("{} is running", Thread.currentThread().getName());
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

# 为什么需要Runnable接口实现多线程

由于Java为避免棱形问题所以只支持单继承,当一个类已有继承类时,某个函数需要实现异步功能的时候只能通过接口进行拓展,所以才有了Runnable接口。

# Thread和Runnable使用的区别

  1. 继承Thread:线程代码存放在Thread子类的run方法中,调用start()即可实现调用。
  2. Runnable:线程代码存在接口子类的run方法中,需要实例化一个线程对象Thread并将其作为参数传入,才能调用到run方法。

# Thread类中run()和start()的区别

  1. run:仅仅是方法,在线程实例化之后使用run等于一个普通对象的直接调用。
  2. start:开启了线程并执行线程中的run方法,这期间程序才真正执行从用户态到内核态,创建线程的动作。

# Java线程有哪几种状态

新建(NEW):新创建的了一个线程对象,该对象并没有调用start()。 可运行(RUNNABLE):线程对象创建后,并调用了start方法,等待分配CPU时间执行代码逻辑。 阻塞(BLOCKED):阻塞状态,等待锁的释放。当线程在synchronized 中被wait,然后再被唤醒时,若synchronized 有其他线程在执行,那么它就会进入BLOCKED状态。 等待(WAITING):因为某些原因被挂起,等待其他线程通知或者唤醒。 超时等待(TIME_WAITING):等待时间后自行返回,而不像WAITING那样没有通知就一直等待。 终止(TERMINATED):该线程执行完毕,终止状态了。

public enum State {
	      //线程尚未启动
        NEW,

       	//可运行的线程状态,该状态代表的是操作系统中线程状态的ready或者running状态
        RUNNABLE,

        //阻塞等待监视器(synchronized底层的monitor lock实现)或者主动调用wait后被唤醒等待获取监视锁也会处于该状态
        BLOCKED,

       //调用wait挂起等待notify或者notifyAll
        WAITING,

       //设置时限的wait调用挂起,可能是调用下面某个方法
       //Thread.sleep
	   //Object.wait with timeout
	   //Thread.join with timeout
	   //LockSupport.parkNanos
	   //LockSupport.parkUntil
        TIMED_WAITING,

        //线程已完成执行并终止
        TERMINATED;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 和操作系统的线程状态的区别

如下图所示,实际上操作系统层面可将RUNNABLE分为Running以及Ready,Java设计者之所以没有区分那么细是因为现代计算机执行效率非常高,这两个状态在宏观角度几乎无法感知。现代操作系统对多线程采用时间分片的抢占式调度算法,使得每个线程得到CPU在10-20ms 处于运行状态,然后在让出CPU时间片,在不久后又会被调度执行,所以对于这种微观状态区别,Java设计者认为没有必要为了这么一瞬间进行这么多的状态划分。

# 什么是上下文切换

线程在执行过程中都会有自己的运行条件和状态,这些运行条件和状态我们就称之为线程上下文,这些信息例如程序计数器、虚拟机栈、本地方法栈等信息。当出现以下几种情况的时候就会从占用CPU状态中退出:

  1. 线程主动让出CPU,例如调用wait或者sleep等方法。
  2. 线程的CPU 时间片用完 而退出CPU占用状态 (因为操作系统为了避免某些线程独占CPU导致其他线程饥饿的情况就设定的例如时间分片算法)。
  3. 线程调用了阻塞类型的系统中断,例如IO请求等。
  4. 线程被终止或者结束运行。

上述的前三种情况都会发生上下文切换。为了保证线程被切换在恢复时能够继续执行,所以上下文切换都需要保存线程当前执行的信息,并恢复下一个要执行线程的现场。这种操作就会占用CPU和内存资源,频繁的进行上下文切换就会导致整体效率低下。

# 线程死锁问题

如下图所示,两个线程各自持有一把锁,必须拿到对方手中那把锁才能释放自己的锁,正是这样一种双方僵持的状态就会导致线程死锁问题。

翻译称代码就如下图所示:

public class DeadLockDemo {
    public static final Object lock1 = new Object();

    public static final Object lock2 = new Object();


    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock1){
                System.out.println("线程1获得锁1,准备获取锁2");


                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2){
                    System.out.println("线程1获得锁2");
                }
            }
        }).start();


        new Thread(() -> {
            synchronized (lock2){
                System.out.println("线程2获得锁2,准备获取锁1");

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


                synchronized (lock1){
                    System.out.println("线程2获得锁1");
                }
            }
        }).start();
    }
}
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

输出结果

线程1获得锁1,准备获取锁2
线程2获得锁2,准备获取锁1
1
2

符合以下4个条件的场景就会发生死锁问题:

  1. 互斥:一个资源任意时间只能被一个线程获取。
  2. 请求与保持条件:一个线程拿到资源后,在获取其他资源而进入阻塞期间,不会释放已有资源。
  3. 不可剥夺条件:该资源被线程使用时,其他线程无法剥夺该线程使用权,除非这个线程主动释放。
  4. 循环等待条件:若干线程获取资源时,取锁的流程构成一个头尾相接的环,如上图。

预防死锁的3种方式

  1. 破坏请求与保持条件:以上面代码为例,我们要求所有线程必须一次性获得两个锁才能进行业务处理。即要求线程一次性获得所有资源才能进行逻辑处理。
  2. 破坏不可剥夺:资源被其他线程获取时,我们可以强行剥夺使用权。
  3. 破坏循环等待:这个就比较巧妙了,例如我们上面lock1 id为1,lock2id为2,我们让每个线程取锁时都按照lock的id顺序取锁,这样就避免构成循环队列。
  4. 操作系统思想(银行家算法):这个就涉及到操作系统知识了,大抵的意思是在取锁之前对资源分配进行评估,如果在给定资源情况下不能完成业务逻辑,那么就避免这个线程取锁,感兴趣的读者可以

# sleep和wait方法区别

  1. sleep不会释放锁,只是单纯休眠一会。而wait则会释放锁。
  2. sleep单纯让线程休眠,在给定时间后就会苏醒,而wait若没有设定时间的话,只能通过notify或者notifyAll唤醒。
  3. sleep是Thread 的方法,而wait是Object 的方法
  4. wait常用于线程之间的通信或者交互,而sleep单纯让线程让出执行权。

# 为什么sleep会定义在Thread

因为sleep要做的仅仅是让线程休眠,所以不涉及任何锁释放等逻辑,放在Thread上最合适。

# 为什么wait会定义在Object 上

我们都知道使用wait时就会释放锁,并让对象进入WAITING 状态,会涉及到资源释放等问题,所以我们需要将wait放在Object 类上。

# 可以直接调用 Thread 类的 run 方法吗?

若我们编写run方法,然后调用Thread 的start方法,线程就会从用户态转内核态创建线程,并在获取CPU时间片的时候开始运行,然后运行run方法。 若直接调用run方法,那么该方法和普通方法没有任何差别,它仅仅是一个名字为run的普通方法。

# 假如在进程中, 已经开辟了多个线程, 其中一个线程怎么中断其它线程?

找到线程对应线程组并基于线程id即可定位到线程,然后调用interrupt将其打断即可:

	public static Thread getThreadById(long threadId) {
        //获取线程对应线程组
        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
        //比对id定位线程
        if (threadGroup != null) {
            Thread[] threads = new Thread[(int) (threadGroup.activeCount() * 1.2)];
            //获取线程组中获取的线程数
            int count = threadGroup.enumerate(threads, true);
            for (int i = 0; i < count; i++) {
                if (threads[i].getId() == threadId) {
                    return threads[i];
                }
            }
        }

        throw new RuntimeException("未找到线程");
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

对应的我们也给出使用示例,感兴趣的读者可自行参阅注释了解实现细节:

//创建含有2个线程的线程池
    private static final ExecutorService threadPool = Executors.newFixedThreadPool(2);
    //记录用于打断的线程id
    private static Long threadId;


    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        //线程1无限休眠,直到被打断
        threadPool.execute(() -> {
            Console.log("线程池线程启动执行,线程id:{}", Thread.currentThread().getId());
            threadId = Thread.currentThread().getId();
            try {
                TimeUnit.DAYS.sleep(1);
            } catch (InterruptedException e) {
                Console.error("当前线程被打断,线程id:{}", Thread.currentThread().getId(), e);
            } finally {
                countDownLatch.countDown();
            }
        });

        //线程2用于打断线程1
        threadPool.execute(() -> {
            while (true) {
                if (threadId != null) {
                    Console.log("打断线程,线程id:{}", threadId);
                    getThreadById(threadId).interrupt();
                    countDownLatch.countDown();
                    break;
                }
                ThreadUtil.sleep(5000);
            }
        });

        countDownLatch.await();
        threadPool.shutdownNow();
    }
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

对应输出结果如下,可以看到threadId 非空时,线程2就会将休眠的线程1打断:

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

由于该问题的篇幅比较大,笔者专门写了一篇文章来讨论这两个问题,感兴趣的朋友可以看看: IO任务与CPU调度艺术:https://mp.weixin.qq.com/s/le3nBV0JpUhW6rzVtvBHmw (opens new window)

# 小结

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

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

# 参考

Java并发编程:volatile关键字解析:https://www.cnblogs.com/dolphin0520/p/3920373.html (opens new window)

图解 | 你管这破玩意叫线程池?: https://mp.weixin.qq.com/s?__biz=Mzk0MjE3NDE0Ng==&mid=2247491549&idx=1&sn=1d5728754e8c06a621bbdca336d85452&chksm=c2c66570f5b1ec66df623e5300084257bd943b134d34e16abaacdb58834702dbbc4599868b89&scene=178&cur_album_id=1703494881072955395#rd (opens new window)

我是一个线程:https://mp.weixin.qq.com/s/IkNfuE541Mqqbv2iLIhMRQ (opens new window)

Java 并发常见面试题总结(上):https://javaguide.cn/java/concurrent/java-concurrent-questions-01.html#什么是线程和进程 (opens new window)

创建线程几种方式_线程创建的四种方式及其区别:https://cloud.tencent.com/developer/article/2135189 (opens new window)

《Java并发编程实战》

揭秘 CPU 缓存:L1、L2 和 L3 的性能秘密:https://www.sysgeek.cn/what-is-cpu-cache/ (opens new window)

编辑 (opens new window)
上次更新: 2026/03/26, 01:05:31
四级文件(测试)
深入理解Java中的final关键字

← 四级文件(测试) 深入理解Java中的final关键字→

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