单核CPU是否有线程可见性问题?
本文仅是本人对问题的思考记录,并没有实操验证,有误请大家评论指出。
今天见到了一个经典的问题,单核CPU是否有线程可见性问题,学完操作系统应该可以直接回答,不会有线程安全问题。但如果结合JVM虚拟机来进行分析,就又麻烦了一点。结合线程切换和JVM内存区域,本文重新梳理了一下对这个问题的思考。
文中可能还有其他问题,例如JDK8之后就没有方法区的概念了,
以及最后的data=1只是为了表示对数据进行更改,没有考虑变量存储位置和引用类型等问题。
1 从JVM内存区域说起
我们都知道,JVM的内存区域有两部分,一部分是线程共享的:堆和方法区(JDK8之后变为元空间的概念,且位于本地内存中),另一部分是线程私有的:虚拟机栈、本地方法栈、程序计数器。
既然是线程私有的,那么如果有多线程,难道有多个虚拟机栈、本地方法栈、程序计数器吗?答案是否定的。一个JVM虚拟机的内存区域就是这样的。那么问题来了,一个线程占有了这三个私有区,那其他线程到哪去了?我们需要回顾操作系统中的线程和进程间的关系:
线程和进程的关系
王道书里是这么解释的:进程是一个独立的运行单位,也是操作系统进行资源分配的基本单位。而线程不拥有资源,但却是CPU调度的最小单位。
进程。它包含三个部分:PCB(Process Control Block)进程控制块、程序段、数据段。其中PCB为核心,该结构常驻内存,与进程同生共死。切换进程的时候,处理机状态信息必须保存到响应的PCB中,以便在该进程重新执行时,能从断点继续执行。
线程。除了有TCB(Thread Control Block)线程控制块之外,还有其专有的存储区。切换线程的时候,状态信息需要保存到TCB中。
这里插入思考一个问题,为什么线程切换比进程切换更轻量级?
回答这个问题,我们首先要知道“保存状态信息”到底保存了什么?由于一个进程用到的数据都会加载到内存中(可能只加载了一部分,通过虚拟内存),所以PCB中保存的都是一些地址引用,而不是数据实体。大概包括:
- 进程描述信息:PID、UID
- 进程控制和管理信息:进程状态、优先级、代码入口地址、CPU占用时间、程序的外存地址等
- 资源分配清单:I/O设备信息、代码段指针、数据段指针、堆栈指针、文件描述符等
- CPU相关信息:各种寄存器的值、状态字
即便是只需要保存地址引用,但仍然需要保存这么多的信息。而线程切换时,TCB需要保存的,相比之下就轻量级很多:
- PC程序计数器(寄存器)、栈等信息。
这两个一对比,就发现了切换的代价,线程切换明显更轻。
线程与进程在Android中的表现
一个Java程序,通过main()开启了它的进程生涯,承载这个程序的,就是JVM虚拟机。可以理解为,JVM虚拟机就是这个java程序的进程的概念。
Android的进程是什么呢?在Zygote孵化器 fork() 进程的时候,我们发现它还fork()了虚拟机,准备好虚拟机后,反射执行了ActivityThread的main()方法,换句话说,Android中一样也是一个虚拟机对应了一个APP进程。
APP进程中可能有很多线程,比如binder线程、用户自己开辟的线程。这些线程的切换,都通过TCB来进行现场的保存与恢复。虚拟机栈、本地方法栈、程序计数器在内存中的地址信息都会通过TCB来进行保存和恢复。
我们还要注意到,TCB和PCB的保存与恢复类似,都是保存数据地址,例如栈信息的数据地址、寄存器的数据等。线程的栈中有局部变量表,它通过指针,JVM中线程共享的部分获取数据,它本身并不存储数据。线程私有的,是线程各自的局部变量表、程序计数器等信息,而并不是数据本身。例如下面这张图所示的虚拟机栈内部结构,都是一些指针信息,并没有保存数据本体。
到这里,我们来梳理一下:
- App进程的切换,本质上是对该进程的PCB(程序控制器)的信息进行保存与恢复。其信息具体指向的内容,仍然还在内存中。
- 线程的切换,如果是同一进程下的线程切换,本质上是对线程的TCB(线程控制器)的信息进行保存与恢复。其信息具体指向的内容,仍然在对应进程所拥有的内存空间中。
我们来看到下面这张图:
为了结构清晰,这里没有加入多级Cache缓存,主要为了表达,如果仅是线程切换,只需要切换TCB的信息即可,也就是从上图蓝线引用切换为粉线引用,而PCB的信息则不需要改动。当然,如果要切换其他进程,就需要对PCB进行保存和恢复了。
到这,我们最初的问题:“一个JVM虚拟机的内存区域就是这样的,一个线程占有了虚拟机栈、本地方法栈、程序计数器,那其他线程到哪去了?”。可以回答,其他线程的虚拟机栈、本地方法栈、程序计数器还在内存中,这三者在内存中的地址信息存在了TCB(线程控制块)中。当这个线程需要被调度的时候,通过TCB的地址信息,将这个线程的数据恢复回来。
Java的线程是如何实现的?
我们从启动线程的源码中也能看到,start()最终调用到start0()这个本地方法,这就说明Java线程的启动是由JVM底层来决定的。又提到王道操作系统的内容,我们知道线程分为:用户线程、核心线程、组合线程。Java线程的实现方式随不同操作系统而异。
在《深入理解JVM虚拟机》中说到,例如Windows和Linux中,都是使用一对一的线程模型来实现的,一条Java线程对应一条轻量级进程。在Unix平台中,可以支持一对一以及多对多。但都没有使用用户线程,主要原因是,如果使用用户线程,进程就要自己处理线程的创建 、切换和销毁,但最重要的麻烦在于,很难处理:“阻塞如何处理”、“多处理器时,如何将线程映射到其他处理器上并行执行”。
下图给出用户线程与轻量级进程1:1的关系,其中UT为用户线程(User Thread)同时也是Java线程,LWP为轻量级进程(LowWeight Process),KLT为内核线程(Kernel-Level Thread):
也正是有内核线程的参与,即由操作系统介入,把线程作为最小单位进行调度,才使得在多核CPU下,一个Java进程的多个线程,可以运行在不同的处理机上。
2. 多核CPU的结构图
铺垫了这么多,仍然不能切入主题,在讨论线程间可见性问题的时候,避不开缓存一致性问题。我们知道,ALU计算单元的处理速度远高于内存数据的读写速度,所以我们引入了多级缓存,来减小这种效率差。为了节省时间,我们直接考虑一个JVM虚拟机进程,在多级缓存中是如何表现的,且我只画了二级缓存:
主存的数据很多,缓存中只根据局部性原理留下了几个物理块。我们先来梳理几个概念,再进入线程间可见性问题。
首先,JVM内存区域中的堆和方法区的“共享”概念,在上面这张图中就体现了。不同的线程并行跑在不同的CPU中,访问的都是JVM内存区域中的“共享”区。但是,由于多级缓存,Java线程访问到的只是一份数据拷贝,并不是真正对主存的数据做处理。我们首先明确一点,缓存中的数据是真拷贝,缓存是从主存中拷贝了一整个物理块到缓存中来,而不是保存了指向主存的指针。
在不同CPU中对共享区域数据的修改,在使用“写回法”的情况下,只要没出现物理块的调换,就不会将更改后的数据更新回主存。其他线程也根本不知道这部分数据被改了:
这久发生了线程安全问题,到底应该听谁的呢?最后写回主存的时候就会无法确认最后的data到底是2还是3。
Java中通过volatile、final、synchronized关键字来实现线程间可见性
为了解决这种缓存一致性问题,java引入了volatile、final、synchronized关键字来实现线程间可见性问题。具体如何做到的,这里就不做多余分析了。通过线程间可见性关键字,让对变量的访问需要强制从内存中重新把数据刷回。
3. 最后回到正题,单核CPU会有线程可见性问题么?
答案是没有的。还是刚才的例子,现在只有CPU-1,首先来到线程A,将data从1改为2:
接下来,进行线程切换,线程私有区被切换为了线程B的内容,但共有部分并不需要切换,这是进程资源,只有在进程切换,或者缺页等情况发生的时候,才会有所变动。
轮到线程B执行,线程B也要访问data变量,通过局部变量表的指针,找到了位于堆中data的位置,
由于线程B访问的也是这一块缓存,即便是没有缓存一致性协议,它仍然能够访问到最新的值,因为这里根本就不存在同一级别的缓存出现不一致的情况,因为它最终层级只有一个缓存。至此,就搞通了为什么单核CPU下的多线程场景,不会造成线程安全,不会有线程可见性问题。
当然,在进行进程切换时,或者发生缓存缺页时,进程的物理块可能会被逐层写回主存,但这并不影响我们之前的分析。
此外,本文举的data例子是int变量,暂且认为是一个对象中的某个成员变量,所以修改的是堆中某个对象的成员变量的值,避免掺杂其他问题。
单核CPU还有必要开启多线程吗?
还是有必要的,举个例子,其中一个线程需要进行I/O操作,不希望在IO还没完成的时候把CPU时间交给它,浪费CPU资源。
4. 参考文献
《深入理解Java虚拟机》,周志明
《王道操作系统考研复习指导》,王道论坛
相关文章:

单核CPU是否有线程可见性问题?
本文仅是本人对问题的思考记录,并没有实操验证,有误请大家评论指出。 今天见到了一个经典的问题,单核CPU是否有线程可见性问题,学完操作系统应该可以直接回答,不会有线程安全问题。但如果结合JVM虚拟机来进行分析&…...

MyBatis 架构介绍
MyBatis 架构介绍MyBatis 架构图MyBatis 所解决的 JDBC 中存在的问题引用MyBatis 架构图 mybatis 配置:mybatis-config.xml,此文件作为 mybatis 的全局配置文件,配置了 mybatis 的运行环境等信息。另一个 mapper.xml 文件即 sql 映射文件,文件…...
加密算法---RSA 非对称加密原理及使用
加密算法---RSA 非对称加密原理及使用一 非对称加密原理介绍二 加密解密测试2.1 加密解密工具类2.2 测试一 非对称加密原理介绍 非对称加密算法中,有两个密钥:公钥和私钥。它们是一对,如果用公钥进行加密,只有用对应的私钥才能解…...
MySQL-查询语句
数据库管理系统的一个最重要的功能就是数据查询,数据查询不应只是简单查询数据库中存储的数据,还应该根据需要对数据进行筛选,以及确定数据以什么样的格式显示。MySQL提供了功能强大、灵活的语句来实现这些操作。下面是通过help帮助查看到的s…...
【算法】【数组与矩阵模块】求数组中需要排序的最短子数组长度
目录前言问题介绍解决方案代码编写java语言版本c语言版本c语言版本思考感悟写在最后前言 当前所有算法都使用测试用例运行过,但是不保证100%的测试用例,如果存在问题务必联系批评指正~ 在此感谢左大神让我对算法有了新的感悟认识! 问题介绍 …...

centos安装Anaconda3
目录一、参考二、Anaconda简介1、用途2、关于anaconda三、下载安装1、下载2、安装anaconda3、配置环境遍历4、测试配置结果5、设置显示前缀一、参考 在centos上安装Anaconda 最新Anaconda3的安装配置及使用教程(附图文) 二、Anaconda简介 一句话&…...

【微信小程序】-- WXML 模板语法 - 列表渲染 -- wx:for wx:key(十二)
💌 所属专栏:【微信小程序开发教程】 😀 作 者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! &…...

【Linux】Linux中gcc/g++的使用
本期主题:程序的编译过程和gcc/g的使用博客主页:小峰同学分享小编的在Linux中学习到的知识和遇到的问题小编的能力有限,出现错误希望大家不吝赐🍁 1.背景知识 预处理(进行宏替换,去注释,头文件的…...

【Spring Cloud Alibaba】(五)Dubbo启动报错?一直重连报错?你值得学习的是排查问题的方法
系列目录 【Spring Cloud Alibaba】(一)微服务介绍 及 Nacos注册中心实战 【Spring Cloud Alibaba】(二)微服务调用组件Feign原理实战 【Spring Cloud Alibaba】(三)OpenFeign扩展点实战 源码详解 【Spri…...
adb命令的使用
命令 连接机顶盒 adb connect [机顶盒ip]查看已连接设备 adb devices断开某个机顶盒的连接 adb disconnect [机顶盒ip] or adb disconnect [虚拟机名称]断开所有设备连接 adb disconnect获取 root 权限 adb root挂载文件系统 adb remount当想往移动设备端 push 文件时显…...
springBoot自定义参数类型转换器
springBoot允许用户自定义转换器,以处理自定义请求参数协议。 方式一:通过实现接口:WebMvcConfigurer 并重写方法的形式。 Configuration public class BootConfig implements WebMvcConfigurer {/*** 自定义参数转换*/Overridepublic voi…...
OA系统在企业中的应用你知道哪些?
随着人工智能技术的不断发展,企业中的OA系统(Office Automation System)正在逐渐得到广泛应用。OA系统是一种集成了多种功能的信息化工具,能够帮助企业实现办公自动化、信息管理、决策支持等多种功能。本文将从OA系统在企业中的应…...
JAVA中,ArrayList 的扩容机制,含案例
JAVA中,ArrayList 的扩容机制,含案例 在 Java 中,ArrayList 是一个动态数组,它可以根据需要自动增长。当 ArrayList 中的元素数量超过其初始容量时,它会重新分配一个更大的内部数组,然后将现有元素复制到新…...

供应链的有效管理,分析指标有哪些
对于企业而言,供应链是一个很复杂的、体系化的生态系统,从原材料、到供应商、到生产、仓库、物流,最后到达经销商或者最终客户那里,这个链条很长。相关的分析指标也有很多,在这些指标里面也有非常多可以扩展、延申的内…...

嵌入式环境配置—VMware 软件安装和虚拟机的创建
目录 一、VMware软件的安装 二、虚拟机的创建 三、Linux操作系统的安装 VMware软件的安装 为什么要虚拟机? 嵌入式Linux开发需要在Linux系统下进行,我们选择了Ubuntu。 1.双系统安装 有问题,一次只能使用一个系统。Ubuntu基本只做编译用。需求&…...

阿里前端二面经典手写面试题汇总
实现类的继承 实现类的继承-简版 类的继承在几年前是重点内容,有n种继承方式各有优劣,es6普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想…...

【Eye】Fake News Reading on Social Media: An Eye-tracking Study
Fake News Reading on Social Media: An Eye-tracking Study Abstract 在网上传播假新闻(以及一般的虚假信息)最近被认为是威胁整个社会的一个主要问题。这种传播在很大程度上是由于新的媒体形式,即社交网络和在线媒体网站。研究人员和从业…...

想学计算机,应该学什么专业?
我们在考虑想学计算机,应该学什么专业?这个问题的时候,每个人都应该结合自己的兴趣来确定。有的喜欢编程、有的喜欢设计、有的喜欢做产品跟人打交道……自己有兴趣再加上自己的努力,掌握好专业技能,就一定能进入高薪的…...

Android逆向之旅—反编译利器Apktool使用教程
apktool下载软件首先下载apktool.bat和apktool.jar官网地址:https://ibotpeaches.github.io/Apktool/install/配置环境变量具体的apktool命令自行百度apktool 解包与打包解包: apktool d xxx.apk打包: apktool b xxx1.jadx安装与使用下载exe或…...

色环电阻的阻值如何识别
这种是色环电阻,其外表有一圈圈不同颜色的色环,现在在一些电器和电源电路中还有使用。下面的两种色环电阻它颜色还不一样,一个蓝色,一个土黄色,其实这个蓝色的属于金属膜色环电阻,外表涂的是一层金属膜&…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...

如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...