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

单核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中线程共享的部分获取数据,它本身并不存储数据。线程私有的,是线程各自的局部变量表、程序计数器等信息,而并不是数据本身。例如下面这张图所示的虚拟机栈内部结构,都是一些指针信息,并没有保存数据本体。

img

到这里,我们来梳理一下:

  1. App进程的切换,本质上是对该进程的PCB(程序控制器)的信息进行保存与恢复。其信息具体指向的内容,仍然还在内存中。
  2. 线程的切换,如果是同一进程下的线程切换,本质上是对线程的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或…...

色环电阻的阻值如何识别

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

Dataway 让 Spring Boot 不再需要 Controller、Service、DAO、Mapper 简单接口直接开发。

新的sql语法可以先看一下官网&#xff0c;部署起来之后会用到Dataql&#xff1a; DataQL - 数据查询语言https://www.dataql.net/先看一下效果 接下来来实现一下。 1 创建spring boot项目 导入依赖 <!--begin dataWay--><!--hasor-spring 负责 Spring 和 Hasor 框架之…...

C#窗口介绍

窗口就是打开程序我们所面对的一个面板&#xff0c;里面可以添加各种控件&#xff0c;如下图所示&#xff0c;我们可以在属性栏设置其标题名称、图标、大小等。图1 窗口图 图2 设置面板 图3 设置双击标题框&#xff0c;会生成Load函数&#xff0c;也可以到事件里面去找Load函数…...

SpringBoot:SpringBoot整合Junit 和 MyBatis(3)

SpringBoot整合Junit 和 MyBatis1. SpringBoot整合Junit2. SpringBoot整合MyBatis2.1 定义SpringBoot项目2.2 定义dao接口2.3 定义service层2.4 定义controller层2.5 配置yml/yaml文件2.6 postman测试1. SpringBoot整合Junit 在com.example.service下创建BookService接口 publ…...

Web自动化测试框架Selenium

作者&#xff1a;霍格沃兹测试开发学社 链接&#xff1a;https://www.zhihu.com/question/59854292/answer/2827875817 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 什么是自动化测试 自动化测试就是&#xff0…...

大数据系统自检

第一章 大数据计算系统概述 1.1 大数据计算框架概述 Hadoop Hadoop的运行过程&#xff08;5个步骤&#xff1f;&#xff09; split > map > shuffle > reduce > output Hadoop的详细运行过程&#xff1f;&#xff08;4个大过程&#xff0c;6662&#xff09; 创建…...

MySQL数据库操作

查看数据库语法show databases——列出所有的数据库 show databases [ like wild ];——列出和字符串wild名字相同的数据库 这里可以配合SQl的 "%" 和 "_" 通配符使用来查找多个数据库在SQL语句中"%"代表任意字符出现任意次数,"_"代表…...

线程安全实例分析

一、变量的线程安全分析 成员变量和静态变量是否线程安全&#xff1f; ● 如果它们没有共享&#xff0c;则线程安全 ● 如果它们被共享了&#xff0c;根据它们的状态是否能够改变&#xff0c;又分两种情况 —— 如果只有读操作&#xff0c;则线程安全 —— 如果有读写操作&am…...

Tomcat源码分析-启动分析(二) Catalina初始化

Bootstrap Tomcat运行是通过Bootstrap的main方法&#xff0c;在开发工具中&#xff0c;我们只需要运行Bootstrap的main方法&#xff0c;便可以启动tomcat进行代码调试和分析。Bootstrap是tomcat的入口&#xff0c;它会完成初始化ClassLoader&#xff0c;实例化Catalina以及loa…...

基础复习第二十二天 泛型的使用

泛型JDK1.5设计了泛型的概念。泛型即为“类型参数”&#xff0c;这个类型参数在声明它的类、接口或方法中&#xff0c;代表未知的通用的类型。例如&#xff1a;java.lang.Comparable接口和java.util.Comparator接口&#xff0c;是用于对象比较大小的规范接口&#xff0c;这两个…...

【C++进阶】三、二叉搜索树

目录 一、二叉搜索树 1.1 概念 1.2 二叉搜索树操作 二、二叉搜索树实现 2.1 框架总览 2.2 实现接口总览 2.2.1 构造函数 2.2.2 拷贝构造 2.2.3 赋值重载 2.2.4 析构函数 2.2.5 二叉搜索树的遍历 2.2.6 插入函数 2.2.7 查找函数 2.2.8 删除函数 2.3 二叉搜索数完整…...