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

简介: 导致可见性的原因有很多,如: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. 。。。

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

五、最后说一句

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

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

相关文章
|
4月前
|
算法 Java 编译器
多线程线程安全问题之系统层面的锁优化有哪些常见的策略
多线程线程安全问题之系统层面的锁优化有哪些常见的策略
|
5月前
|
缓存 Java 程序员
Java内存模型深度解析:可见性、有序性和原子性
在多线程编程中,正确理解Java内存模型对于编写高效且无bug的并行程序至关重要。本文将深入探讨JMM的三大核心特性:可见性、有序性和原子性,并结合实例分析如何利用这些特性来避免常见的并发问题。
60 1
|
4月前
|
安全 Java
Volatile不保证原子性及解决方案
**原子性在并发编程中确保操作不可中断,保持数据一致性。volatile保证可见性但不保证原子性,如`count++`在多线程环境下仍可能导致数据不一致。解决方案包括使用`synchronized`、`AtomicInteger`或`ReentrantLock`来确保复合操作的原子性和线程安全。例子展示了volatile在并发自增中的局限性,实际值通常小于预期,强调了正确选择同步机制的重要性。**
|
Java 编译器 程序员
JMM的内存可见性保证
JMM的内存可见性保证
52 0
|
存储 缓存 安全
并发三要素 : 可见性, 原子性, 有序性
并发三要素:可见性, 原子性, 有序性,并发问题该怎样解决,怎样实现数据同步,这篇文章为您解决
116 0
|
缓存 安全 Java
遵循Happens-Before规则来保证可见性|而非掌握所有底层
基于JSR -133内存模型提出了happens-before的概念,通过这个概念来阐述操作之间的内存可见性。要保证可见性,就是遵守 Happens-Before 规则,合理的使用java提供的工具。
162 0
|
缓存 Java 中间件
并发三大特性——可见性
并发三大特性——可见性
185 0
并发三大特性——可见性
|
存储 设计模式 Java
对内存可见性造成影响的代码
我们在开发过程中,是不是频繁的写一些System.out.println()来验证程序的执行?切记在正式环境将打印语句去除!
111 2
对内存可见性造成影响的代码
|
缓存 安全 Java
Java并发编程 - volatile 怎么保障内存可见性 & 防止指令重排序?
Java并发编程 - volatile 怎么保障内存可见性 & 防止指令重排序?
159 0
Java并发编程 - volatile 怎么保障内存可见性 & 防止指令重排序?
|
存储 缓存 Java
【高并发】如何解决可见性和有序性问题?这次彻底懂了!
之前,我们详细介绍了导致并发编程出现各种诡异问题的三个“幕后黑手”,接下来,我们就开始手撕这三个“幕后黑手”,让并发编程不再困难! 今天,我们先来看看在Java中是如何解决线程的可见性和有序性问题的,说到这,就不得不提一个Java的核心技术,那就是——Java的内存模型。 如果编写的并发程序出现问题时,很难通过调试来解决相应的问题,此时,需要一行行的检查代码,这个时候,如果充分理解并掌握了Java的内存模型,你就能够很快分析并定位出问题所在。
408 1
【高并发】如何解决可见性和有序性问题?这次彻底懂了!