原来规避重排序是为了保障可见性

简介: 导致可见性的原因有很多,如:1. 为了提升性能而实施的编译期重排序。2. 数据在寄存器中。3. cpu缓存的更改未同步到主内存中 或 内存中的更改未同步到cpu缓存(运行期重排序)。
我是石页兄,朋友不因远而疏,高山不隔友谊情;偶遇美羊羊,我们互相鼓励

欢迎关注微信公众号「架构染色」交流和学习

一、了解重排序

为了性能,编译时和运行时都会有重排序,造成指令执行顺序变了,宏观上从这3点了解重排序:

  1. 线程内有序:如果再本线程内观察,所有的操作都是有序的,即线程内表现为串行的语义(Within Thread As-If-Serial Semantics)。
  2. 线程间无序:如果再一个线程中观察另一个线程,所有的操作都是无序的,即 指指令重排序现象和工作内存与主内存同步延迟现象。
  3. 总会有重排序:指令重排序在任何时候都有可能发生,与是否为多线程无关,之所以在单线程下感觉没有发生重排序,是因为线程内表现为串行的语义的存在。

二、重排序遵守 as-if-serial语义

as-if-serial语义 : 不管怎么重排序(编译器和处理器为了提高并行度),程序在单线程中的执行结果不会改变。

编译器、runtime和处理器都必须遵守as-if-serial语义:

  1. 为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。
  2. 如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序

正确的理解as-if-serial语义的功效:

  1. 如果在本线程内观察,所有的操作都是有序的;
  2. 如果在一个线程中观察另一个线程,所有的操作都是无序的。

三、重排序的类型

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

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

image.png

从编译运行视角分为两类:

  1. 编译期重排序:包括 编译器优化的重排序。
  2. 运行期重排序:包括 指令级并行的重排序,内存系统的重排序。

四、为何要禁止重排序

修复Java内存模型,第2部分里的一段话开始思考

理解JMM所需的关键概念之一是可见性 -您如何知道如果线程A执行someVariable = 3,其他线程将看到线程A在其中 写入的值3?存在许多原因,为什么另一个线程可能不会立即为以下值看到值3 someVariable:可能是因为编译器已对指令进行了重新排序以便更有效地执行,或者已将someVariable其缓存在寄存器中,或者将其值写入了缓存在写处理器上但尚未刷新到主内存,或者在读处理器的缓存中有旧的(或陈旧的)值。内存模型确定线程何时可以可靠地“看到”对其他线程所做的变量的写入。特别是,内存模型为定义了语义volatile,synchronized,final这确保了跨线程的内存操作可见性。

从其原文中不难看出,导致可见性的原因有很多

  1. 为了提升性能而实施的编译期重排序。
  2. 数据在寄存器中。
  3. cpu缓存的更改未同步到主内存中 或 内存中的更改未同步到cpu缓存(运行期重排序)。
  4. 。。。

其中两次提到重排序会导致可见性问题。那么某些情况下为了保证可见性则需禁止重排序

五、最后说一句

我是石页兄,如果这篇文章对您有帮助,或者有所启发的话,欢迎关注笔者的微信公众号【 架构染色 】进行交流和学习。您的支持是我坚持写作最大的动力。

欢迎点击链接扫马儿关注、交流。

相关文章
|
9月前
|
存储 监控 安全
解锁ThreadLocal的问题集:如何规避多线程中的坑
解锁ThreadLocal的问题集:如何规避多线程中的坑
436 0
|
3月前
|
监控 安全 定位技术
《C++新特性:为多线程数据竞争检测与预防保驾护航》
多线程编程是提升软件性能的关键,但数据竞争问题却是一大挑战。C++新特性如增强的原子类型和完善的内存模型,为检测和预防数据竞争提供了有力支持。这些改进不仅提高了程序的可靠性,还提升了开发效率,使多线程编程更加安全高效。
77 19
|
4月前
|
监控 安全 算法
线程死循环确实是多线程编程中的一个常见问题,在编码阶段规避潜在风险
【10月更文挑战第12天】线程死循环确实是多线程编程中的一个常见问题,在编码阶段规避潜在风险
79 2
|
7月前
|
算法 Java 编译器
多线程线程安全问题之系统层面的锁优化有哪些常见的策略
多线程线程安全问题之系统层面的锁优化有哪些常见的策略
|
8月前
|
缓存 Java 程序员
Java内存模型深度解析:可见性、有序性和原子性
在多线程编程中,正确理解Java内存模型对于编写高效且无bug的并行程序至关重要。本文将深入探讨JMM的三大核心特性:可见性、有序性和原子性,并结合实例分析如何利用这些特性来避免常见的并发问题。
81 1
|
7月前
|
安全 Java
Volatile不保证原子性及解决方案
**原子性在并发编程中确保操作不可中断,保持数据一致性。volatile保证可见性但不保证原子性,如`count++`在多线程环境下仍可能导致数据不一致。解决方案包括使用`synchronized`、`AtomicInteger`或`ReentrantLock`来确保复合操作的原子性和线程安全。例子展示了volatile在并发自增中的局限性,实际值通常小于预期,强调了正确选择同步机制的重要性。**
|
9月前
|
监控 安全
线程死循环是多线程应用程序开发过程中一个难以忽视的问题,它源于线程在执行过程中因逻辑错误或不可预见的竞争状态而陷入永久运行的状态,严重影响系统的稳定性和资源利用率。那么,如何精准定位并妥善处理线程死循环现象,并在编码阶段就规避潜在风险呢?谈谈你的看法~
避免线程死循环的关键策略包括使用同步机制(如锁和信号量)、减少共享可变状态、设置超时、利用监控工具、定期代码审查和测试、异常处理及设计简洁线程逻辑。通过这些方法,可降低竞态条件、死锁风险,提升程序稳定性和可靠性。
126 0
|
安全 Java
架构系列——面试必问:volatile的可见性、防止指令重排序以及不能保证原子性的解决方式
架构系列——面试必问:volatile的可见性、防止指令重排序以及不能保证原子性的解决方式
|
存储 安全 Java
保障线程安全的设计技术
保障线程安全的设计技术
|
缓存 安全 Java
遵循Happens-Before规则来保证可见性|而非掌握所有底层
基于JSR -133内存模型提出了happens-before的概念,通过这个概念来阐述操作之间的内存可见性。要保证可见性,就是遵守 Happens-Before 规则,合理的使用java提供的工具。
181 0