为什么要出现并发?并发的三要素

大家好,我是"java继父"伯约,假如这篇对大家有帮助的话求一个赞,另外文章末尾放了我从小白到架构师多年的学习资料。

1.为什么需要多线程

众所周知,CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:

  • CPU 增加了缓存,以均衡与内存的速度差异;// 导致 可见性问题
  • 操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;// 导致 原子性问题
  • 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。// 导致 有序性问题

2.线程不安全示例

如果多个线程对同一个共享数据进行访问而不采取同步操作的话,那么操作的结果是不一致的。

以下代码演示了 1000 个线程同时对 cnt 执行自增操作,操作结束之后它的值有可能小于 1000。

public class ThreadUnsafeExample {private int cnt = 0;public void add() {cnt++;}public int get() {return cnt;}
}
public static void main(String[] args) throws InterruptedException {final int threadSize = 1000;ThreadUnsafeExample example = new ThreadUnsafeExample();final CountDownLatch countDownLatch = new CountDownLatch(threadSize);ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i < threadSize; i++) {executorService.execute(() -> {example.add();countDownLatch.countDown();});}countDownLatch.await();executorService.shutdown();System.out.println(example.get());
}
997 // 结果总是小于1000

3.并发出现问题的根源: 并发三要素

上述代码输出为什么不是1000? 并发出现问题的根源是什么?

可见性: CPU缓存引起

可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到。

举个简单的例子,看下面这段代码:

//线程1执行的代码
int i = 0;
i = 10;//线程2执行的代码
j = i;

假若执行线程1的是CPU1,执行线程2的是CPU2。由上面的分析可知,当线程1执行 i =10这句时,会先把i的初始值加载到CPU1的高速缓存中,然后赋值为10,那么在CPU1的高速缓存当中i的值变为10了,却没有立即写入到主存当中。

此时线程2执行 j = i,它会先去主存读取i的值并加载到CPU2的缓存当中,注意此时内存当中i的值还是0,那么就会使得j的值为0,而不是10.

这就是可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值。

原子性: 分时复用引起

原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

举个简单的例子,看下面这段代码:

int i = 1;// 线程1执行
i += 1;// 线程2执行
i += 1;

这里需要注意的是:i += 1需要三条 CPU 指令

  1. 将变量 i 从内存读取到 CPU寄存器;
  2. 在CPU寄存器中执行 i + 1 操作;
  3. 将最后的结果i写入内存(缓存机制导致可能写入的是 CPU 缓存而不是内存)。

由于CPU分时复用(线程切换)的存在,线程1执行了第一条指令后,就切换到线程2执行,假如线程2执行了这三条指令后,再切换会线程1执行后续两条指令,将造成最后写到内存中的i值是2而不是3。

有序性: 重排序引起

有序性:即程序执行的顺序按照代码的先后顺序执行。举个简单的例子,看下面这段代码:

int i = 0;              
boolean flag = false;
i = 1;                //语句1  
flag = true;          //语句2

上面代码定义了一个int型变量,定义了一个boolean类型变量,然后分别对两个变量进行赋值操作。从代码顺序上看,语句1是在语句2前面的,那么JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗? 不一定,为什么呢? 这里可能会发生指令重排序(Instruction Reorder)。

在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。重排序分三种类型:

  • 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  • 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  • 内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

从 java 源代码到最终实际执行的指令序列,会分别经历下面三种重排序:

        上述的 1 属于编译器重排序,2 和 3 属于处理器重排序。这些重排序都可能会导致多线程程序出现内存可见性问题。对于编译器,JMM 的编译器重排序规则会禁止特定类型的编译器重排序(不是所有的编译器重排序都要禁止)。对于处理器重排序,JMM 的处理器重排序规则会要求 java 编译器在生成指令序列时,插入特定类型的内存屏障(memory barriers,intel 称之为 memory fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序(不是所有的处理器重排序都要禁止)。

4.粉丝福利

最新很多同学问我有没有java学习资料,我根据我从小白到架构师多年的学习经验整理出来了一份50W字面试解析文档、简历模板、学习路线图、java必看学习书籍、需要的小伙伴可以关注我的
公众号:“Tom聊架构”,回复暗号:“578”即可获取

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mendelson AS2 介绍下载和配置

最近与一家国外公司做EDI对接&#xff0c;并且EDI通讯工具是基于AS2协议的。目前开源的as2的开源项目有openas2,Mendelson AS2&#xff0c;和国人写的freeas2但是&#xff0c;现在freeas2已经被从开源中国不能下载了&#xff0c;变为收费的版本了。 如果你需要使用基于AS2协议…

Windows 10中蓝牙相关的问题,至少有11种可能的解决办法

蓝牙是一种不可或缺的技术,可以将许多设备无线连接到电脑、笔记本电脑和平板电脑。当它在Windows 10计算机上停止正常工作时,可能有多种原因。要解决这些类型的蓝牙问题,有几种解决方案可以让事情恢复正常。 注意:本文中的信息适用于装有Windows 10的PC和笔记本电脑。 Wi…

Stable Diffusion系列(三):网络分类与选择

文章目录 网络分类模型基座模型衍生模型二次元模型2.5D模型写实风格模型 名称解读 VAELora嵌入文件放置界面使用 网络分类 当使用SD webui绘图时&#xff0c;为了提升绘图质量&#xff0c;可以多种网络混合使用&#xff0c;可选的网络包括了模型、VAE、超网络、Lora和嵌入。 …

亚信安慧AntDB数据库荣获“2023年度信创卓越贡献奖”

近日&#xff0c;业界知名IT垂直门户媒体IT168公布“2023技术卓越奖”获奖名单&#xff0c;亚信安慧AntDB数据库荣获“2023年度信创卓越贡献奖”。 图1&#xff1a;亚信安慧AntDB数据库荣获“2023年度信创卓越贡献奖” “技术卓越奖”评选是IT168重要的年度奖项评选&#xff0c…

spring中的方法调用重试机制

一、Retryable注解 注解方式实现重试机制比较简单&#xff0c;只需要我们在需要重试的方法上加入以下注解 Retryable(value {RemoteAccessException.class}, maxAttempts 3, backoff Backoff(delay 1000)) value&#xff1a;指定需要重试的异常类型。在这个例子中…

RHCE9学习指南 第8章 用户管理

8.1 基本概念 用户在操作系统中是非常重要的一块&#xff0c;我们登录系统&#xff0c;访问共享文件夹等都需要用户进行验证。所以&#xff0c;掌握管理用户的知识是非常有必要的。 说到用户&#xff0c;我们会提到两个名词账户信息和木马信息。 账户信息&#xff1a;我们可以…

【技术科普】CPU、GPU、TPU、NPU分别是什么?哪个最强?

技术日新月异&#xff0c;物联网、人工智能、深度学习等遍地开花&#xff0c;各类芯片名词CPU&#xff0c;GPU, TPU, NPU层出不穷…它们都是什么&#xff1f;又有着什么千丝万缕的关系和区别&#xff1f; 接下来&#xff0c;统一介绍一下&#xff1a; 01 CPU CPU最早用于计算…

管理团队的5个策略和技巧,让你的团队更高效

管理团队并非一项简单任务&#xff0c;它需要明确的策略和技巧。首先&#xff0c;要明确团队目标&#xff0c;这是所有管理动作的起点。 没有目标&#xff0c;团队就会迷失方向&#xff0c;越努力越容易偏离轨道。设定目标不仅能让团队和员工明确自己的方向&#xff0c;还能帮…

Java网络爬虫拼接姓氏,名字并写出到txt文件(实现随机取名)

目录 1.爬取百家姓1.爬取代码2.爬取效果 2.爬取名字1.筛选男生名字2.筛选女生名字 3.数据处理&#xff08;去除重复&#xff09;4.拼接数据5.将数据写出到文件中 1.爬取百家姓 目标网站&#xff0c;仅作为实验目的。 ①爬取姓氏网站&#xff1a; https://hanyu.baidu.com/shic…

CentOS7安装Java11

文章目录 Java11下载地址卸载OpenJDK查询原系统安装的 JDK根据原系统安装的 JDK 进行卸载命令修改 安装JDK生成JRE Java11下载地址 https://www.oracle.com/java/technologies/javase/jdk11-archive-downloads.html 卸载OpenJDK 查询原系统安装的 JDK java -version yum l…

第十四章 集合(Set)

一、Set 接口&#xff08;P518&#xff09; 1. Set 接口基本介绍 &#xff08;1&#xff09;无序&#xff08;添加和取出的顺序不一致&#xff09;&#xff0c;没有索引。 &#xff08;2&#xff09;不允许重复元素&#xff0c;所以最多包含一个 null。 2. Set 接口的常用方法…

docker安装及入门

笔记来自黑马课堂&#xff1a;【黑马程序员Docker快速入门到项目部署&#xff0c;MySQL部署Nginx部署docker自定义镜像DockerCompose项目实战一套搞定-哔哩哔哩】 https://b23.tv/niWEhEF 一、什么是docker&#xff1a; 快速构建、运行、管理应用的工具。--帮助我们快速部署提…