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

Java多线程与并发

一、并发出现问题的根源: 并发三要素

1. 可见性: CPU缓存引起

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

2. 原子性: 分时复用引起

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

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

这里可能会发生指令重排序(Instruction Reorder)。

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

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

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

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


http://www.kler.cn/a/7475.html

相关文章:

  • 第三百二十三节 Java线程教程 - Java同步器
  • 从零开始学习 sg200x 多核开发之 uboot 网络功能使能
  • C++中 ,new int(10),new int(),new int[10],new int[10]()
  • Hybird和WebView
  • git撤销、回退某个commit的修改
  • 支持向量机SVM——基于分类问题的监督学习算法
  • 把ChatGPT接入我的个人网站
  • Docker容器理解
  • Hadoop学习笔记(持续更新中)
  • Windows 元件
  • 剪格子
  • 映射的概念以及用法
  • 部署ChatGPT(在VPS或免费容器上),无需科学上网!
  • 区间dp算法刷题笔记【蓝桥杯】
  • 【MySQL每日七问】MySQL总结(一)
  • 对标ChatGPT的开源中文方案
  • JSON 与 Ajax
  • 渗透测试综合实验(迂回渗透,入侵企业内网并将其控制为僵尸网络)
  • MySQL用户与权限管理
  • InnoDB 是如何解决幻读的
  • Java方法
  • 优秀测试工程师必须掌握的关系型和非关系型数据库
  • python之socket模块
  • 【Java Web】009 -- MyBatis(入门 增删改查 动态SQL)
  • 【Redis学习】Redis10大数据类型
  • Java并发编程(4) —— Java 内存模型(JMM)详解