【JavaEE面试题(九)线程安全问题的原因和解决方案】

news2024/11/22 23:42:11

多线程-初阶

  • 4. 多线程带来的的风险-线程安全 (重点)
    • 4.1 观察线程不安全
            • 原因是 1.load 2. add 3. save
    • 4.2 线程安全的概念
    • 4.3 线程不安全的原因
            • 最根本的是 操作系统对线程的调度是随机的
            • ★1. 修改共享数据(多个线程修改同一个变量)
            • ★2. 操作不是原子性
            • ★3. 可见性
            • ★4. 代码顺序性
    • 4.4 解决之前的线程不安全问题

4. 多线程带来的的风险-线程安全 (重点)

4.1 观察线程不安全

static class Counter {
    public int count = 0;
    void increase() {
        count++;
   }
}
public static void main(String[] args) throws InterruptedException {
    final Counter counter = new Counter();
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 50000; i++) {
            counter.increase();
       }
   });
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 50000; i++) {
            counter.increase();
       }
   });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(counter.count);
}

大家观察下是否适用多线程的现象是否一致?同时尝试思考下为什么会有这样的现象发生呢?

原因是 1.load 2. add 3. save

在这里插入图片描述

注意:可能会导致 小于5w

4.2 线程安全的概念

想给出一个线程安全的确切定义是复杂的,但我们可以这样认为:
如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的

4.3 线程不安全的原因

在这里插入图片描述

最根本的是 操作系统对线程的调度是随机的
★1. 修改共享数据(多个线程修改同一个变量)

上面的线程不安全的代码中, 涉及到多个线程针对 counter.count 变量进行修改.
此时这个 counter.count 是一个多个线程都能访问到的 “共享数据”
counter.count 这个变量就是在堆上. 因此可以被多个线程共享访问

★2. 操作不是原子性

在这里插入图片描述

在这里插入图片描述
什么是原子性

我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
那我们应该如何解决这个问题呢?是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样就保证了这段代码的原子性了。
有时也把这个现象叫做同步互斥,表示操作是互相排斥的

一条 java 语句不一定是原子的,也不一定只是一条指令

比如刚才我们看到的 n++,其实是由三步操作组成的:

  1. 从内存把数据读到 CPU
  2. 进行数据更新
  3. 把数据写回到 CPU

不保证原子性会给多线程带来什么问题

如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。
这点也和线程的抢占式调度密切相关. 如果线程不是 “抢占” 的, 就算没有原子性, 也问题不大.

★3. 可见性

可见性指, 一个线程对共享变量值的修改,能够及时地被其他线程看到

Java 内存模型 (JMM): Java虚拟机规范中定义了Java内存模型.
目的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果.

在这里插入图片描述

  • 线程之间的共享变量存在 主内存 (Main Memory).
  • 每一个线程都有自己的 “工作内存” (Working Memory) .
  • 当线程要读取一个共享变量的时候, 会先把变量从主内存拷贝到工作内存, 再从工作内存读取数据.
  • 当线程要修改一个共享变量的时候, 也会先修改工作内存中的副本, 再同步回主内存

由于每个线程有自己的工作内存, 这些工作内存中的内容相当于同一个共享变量的 “副本”. 此时修改线程1 的工作内存中的值, 线程2 的工作内存不一定会及时变化

  1. 初始情况下, 两个线程的工作内存内容一致在这里插入图片描述
  2. 一旦线程1 修改了 a 的值, 此时主内存不一定能及时同步. 对应的线程2 的工作内存的 a 的值也不一定能及时同步.在这里插入图片描述
    这个时候代码中就容易出现问题

此时引入了两个问题:

  • 为啥要整这么多内存?
  • 为啥要这么麻烦的拷来拷去?
  1. 为啥整这么多内存?
    实际并没有这么多 “内存”. 这只是 Java 规范中的一个术语, 是属于 “抽象” 的叫法.
    所谓的 “主内存” 才是真正硬件角度的 “内存”. 而所谓的 “工作内存”, 则是指 CPU 的寄存器和高速缓存.
  2. 为啥要这么麻烦的拷来拷去?
    因为 CPU 访问自身寄存器的速度以及高速缓存的速度, 远远超过访问内存的速度(快了 3 - 4 个数量级, 也就是几千倍, 上万倍)

比如某个代码中要连续 10 次读取某个变量的值, 如果 10 次都从内存读, 速度是很慢的. 但是如果
只是第一次从内存读, 读到的结果缓存到 CPU 的某个寄存器中, 那么后 9 次读数据就不必直接访问
内存了. 效率就大大提高了

那么接下来问题又来了, 既然访问寄存器速度这么快, 还要内存干啥??
答案就是一个字: 贵

值的一提的是, 快和慢都是相对的. CPU 访问寄存器速度远远快于内存, 但是内存的访问速度又远远快于硬盘.
对应的, CPU 的价格最贵, 内存次之, 硬盘最便宜

★4. 代码顺序性

什么是代码重排序

一段代码是这样的:

  1. 去前台取下 U 盘
  2. 去教室写 10 分钟作业
  3. 去前台取下快递

如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,按 1->3->2的方式执行,也是没问题,可以少跑一次前台。这种叫做指令重排序
编译器对于指令重排序的前提是 “保持逻辑不发生变化”. 这一点在单线程环境下比较容易判断, 但是在多线程环境下就没那么容易了, 多线程的代码执行复杂程度更高, 编译器很难在编译阶段对代码的执行效果进行预测, 因此激进的重排序很容易导致优化后的逻辑和之前不等价

重排序是一个比较复杂的话题, 涉及到 CPU 以及编译器的一些底层工作原理, 此处不做过多讨论

4.4 解决之前的线程不安全问题

这里用到的机制,我们马上会给大家解释

static class Counter {
    public int count = 0;
    synchronized void increase() {
        count++;
   }
}
public static void main(String[] args) throws InterruptedException {
    final Counter counter = new Counter();
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 50000; i++) {
            counter.increase();
       }
   });
    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 50000; i++) {
            counter.increase();
       }
   });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(counter.count);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/752961.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue使用window.addEventListener 监视网络状态中,箭头函数与function的区别

在vue中使用window.addEventListener监视网络状态时&#xff0c;遇到一个坑&#xff0c;只能说自己跟不是步伐&#xff0c;知识困乏&#xff0c;不知道箭头函数和function函数的区别。 最初vue监视网络状态的方法是这样的&#xff1a; window.addEventListener("online&q…

【C语言】字符串函数

文章目录 一、求字符串长度strlen例子模拟实现 二、长度不受限制的字符串函数strcpy例子模拟实现 strcat例子模拟实现 strcmp例子模拟实现 三、长度受限制的字符串函数strncpy例子 strncat例子 strncmp例子 四、字符串查找strstr例子模拟实现 strtok例子 五、错误信息报告strer…

Linux 这20个 systemd 命令值得运维工程师收藏

systemd是一种Linux系统初始化和管理守护进程的系统和服务管理器。它引入了一组命令行工具&#xff0c;用于管理和监控系统状态、服务单元和日志。 1. systemdctl systemdctl命令用于管理systemd系统和服务单元。以下是一些常用的systemdctl命令&#xff1a; 启动一个服务单…

API 自动化测试指南

目录 前言&#xff1a; 什么是 API 测试&#xff1f; 为什么 API 测试很重要&#xff1f; 测试金字塔 GUI 测试 单元测试 API 测试 API 负载测试 API 测试工具如何选择 如何测试 Web 服务 HTTP 关于 HTTP 请求 请求行&#xff08;HTTP 方法&#xff09; 标头 请…

Jenkins持续集成项目搭建 —— 基于Python Selenium自动化测试

第一步&#xff1a;去官网Jenkins下载最新的war包 第二步&#xff1a;安装.war包即&#xff1a;安装jinkens 打开命令窗口&#xff0c;进入.war包所在的路径下 执行java -jar jenkins.war命令 安装成功的标志如图2所示 在浏览器中试一下是否成功输入&#xff1a;0.0.0.0:8080进…

springboot乒乓球预约管理系统

开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven…

Oracle EBS更新付款银行帐户弹性域

批量更新 应用&#xff1a; 现金管理系统 标题&#xff1a; 银行帐户弹性域 create or replace PROCEDURE ML_UPDATE_BANK_ACCT_INFO(errbuf OUT VARCHAR2,retcode OUT VARCHAR2,ic_user_file IN VARCHAR2 ) ASTYPE T_BANK_ACCT ISRECORD(BANK_ACCOUNT_ID NUMBER(15),-- B…

springboot数码论坛系统

在国外很多发达国家&#xff0c;软件产业早已得到全面普及&#xff0c;但我国经济已不断发展&#xff0c;不断引进国外信息化建设&#xff0c;使国内软件行业得以不断发展&#xff0c;在摸索中进步&#xff0c;最终也得到一些成果&#xff0c;我国的软件业迎来了高速的发展&…

不同conda不同cuda环境变量

文章目录 更改虚拟环境中的环境变量删除虚拟环境的环境变量其他问题&#xff0c;参考&#xff1a;https://blog.csdn.net/mifangdebaise/article/details/124428380 参考了博客 conda虚拟环境内安装CUDA9.0 conda虚拟环境中设置环境变量关于 cuda 的详细安装参见: 记录自己 Ubu…

(CVPR-2019)用于人体姿势估计的深度高分辨率表示学习

用于人体姿势估计的深度高分辨率表示学习 paper题目&#xff1a;Deep High-Resolution Representation Learning for Human Pose Estimation paper是中国科学技术大学发表在CVPR 2019的工作 paper地址 Abstract 在本文中&#xff0c;我们对人体姿势估计问题感兴趣&#xff0c;…

什么是真正的骨传导耳机,列举出几款实用的骨传导耳机

骨传导耳机从发布到现在有了几年的时间&#xff0c;也有很多人开始了解&#xff0c;但真正去选购的人还是很少&#xff0c;如果你没有使用过骨传导耳机&#xff0c;在选购时会很迷茫。作为一个骨传导耳机的重度使用者&#xff0c;下面就给大家分享一下我的体验感受以及选购建议…

OpenCv色彩空间

目录 一、RGB 二、图像处理入门 三、色彩空间的转换 一、RGB 在表示图像时&#xff0c;有多种不同的颜色模型&#xff0c;但最常见的是红、绿、蓝(RGB) 模型RGB 模型是一种加法颜色模型&#xff0c;其中原色 (在RGB模型中&#xff0c;原色是红色 R、绿色 G 和蓝色 B)混合在…

selenium自动化测试工具

Selenium是一个用于测试网站的自动化测试工具&#xff0c;支持各种浏览器包括Chrome、Firefox、Safari等主流界面浏览器&#xff0c;同时也支持phantomJS无界面浏览器。 查看chrome版本&#xff0c;114.05735.199 去 http://chromedriver.storage.googleapis.com/index.html 网…

【从零开始学爬虫】采集全国各地历年房价数据

l 采集网站 【场景描述】采集全国各地历年房价数据。 【源网站介绍】58同城—国内专业的“本地、免费、真实、高效”生活服务平台&#xff01; 【使用工具】前嗅ForeSpider数据采集系统&#xff0c;免费下载&#xff1a; http://www.forenose.com/view/commodity/forespider…

正则表达式与“三贱客”

第三阶段基础 时 间&#xff1a;2023年7月11日 参加人&#xff1a;全班人员 内 容&#xff1a; 正则表达式与“三贱客” 目录 shell脚本的基本应用&#xff1a; 一、正则表达式与grep 2&#xff09;正则表达式的组成 1&#xff09;正则表达式grep常见的选项 2&…

《向量数据库指南》:向量数据库Pinecone关键概念和工作流程

目录 用例 关键概念 向量搜索 向量嵌入 向量数据库 工作流程 定价和部署选项 开始使用 介绍PINECONE向量数据库 Pinecone使构建高性能的向量搜索应用程序变得轻松。 它是一个托管的、云原生的向量数据库,具有简单的API和无需基础架构的优势。 Pinecone具有以下特…

[论文分享]SimMIM:一种简单的掩模图像建模框架

文章地址&#xff1a;https://arxiv.org/abs/2111.09886 代码地址&#xff1a;GitHub - microsoft/SimMIM: This is an official implementation for "SimMIM: A Simple Framework for Masked Image Modeling". 1 摘要 本文介绍了SimMIM&#xff0c;这是一个用于掩模…

React初学者需要的库从哪里下载?

在react官网下载react.js的方法介绍 1、访问react的github官方页面 访问地址为&#xff1a;Downloads | Reacthttps://react-cn.github.io/react/downloads.html 2、点击Download页面中的"Download Starter Kit"按钮&#xff0c;进行下载 学react的时候用到了babe…

波士顿矩阵模型:产品定位

波士顿矩阵 波士 顿 矩 阵 (BCG Matrix) 又称市 场 增 长 率 — 相 对 市 场 份 额 矩 阵 、波士顿咨 询 集 团 法、四象限分析法、 产 品系列 结 构管理法等。 波士 顿 矩 阵是由美国大型商业 咨 询 公司 —— 波士 顿 咨 询 集 团 首 创 的一种 规 划企业产品 组 合的方法。…

什么是统一建模语言(UML)UML与UML类图的基本概念

什么是统一建模语言UML&#xff08;Unified Modeling Language&#xff09; UML&#xff08;统一建模语言&#xff09;是一种通用的建模语言&#xff0c;用于描述软件系统的结构、行为和交互。它提供了一组符号和规则&#xff0c;用于创建可视化的图形模型&#xff0c;帮助开发…