热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

并发编程深度解析(六):volatile关键字详解——as-if-serial指令重排序与内存模型分析

在并发编程中,`as-if-serial`原则确保了即使编译器和处理器对指令进行重排序,单线程的执行结果也不会受到影响。这一原则要求编译器、运行时环境和处理器必须严格遵守,以保证程序的正确性。本文深入探讨了`volatile`关键字的内存模型,详细分析了其在多线程环境中的可见性和有序性特性,以及如何通过`as-if-serial`规则来确保数据的一致性和可靠性。

as-if-serial

不管编译器和处理器怎么重排序,单线程的执行结果都不能被改变。编译器,运行时和处理器都必须遵守as-if-serial

可见性实现原理

volatile 变量的内存可见性是基于内存屏障(Memory Barrier)实现。.

  1. 内存屏障,又称内存栅栏,是一个 CPU 指令。
  2. 在程序运行时,为了提高执行性能,编译器和处理器会对指令进行重排序,JMM 为了保证在不同的编译器和 CPU 上有相同的结果,通过插入特定类型的内存屏障来禁止特定类型的编译器重排序和处理器重排序,插入一条内存屏障会告诉编译器和 CPU:不管什么指令都不能和这条 Memory Barrier 指令重排序。
  • 被volatile修饰编译后,会产生一个LOCK#前缀来对总线加锁其他CPU对内存读写阻塞(总线加锁方式)直到锁释放
  • 后通过缓存一致性协议和嗅探技术来解决的;

请参照上篇文章:并发编程系列(五)volatile关键字详解(1)

禁止指令重排的原理分析

被volatile 修饰的变量禁止指令重排,那这个关键字是如何禁止指令重排序的呢?

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

编译器优化重排序:编译器在不改变单线程语义的前提下,可以安排语句的执行顺序

指令级并行的重排序:现代处理器采用了指令级并行技术(INnstruction-Level Parallelism,ILP),如果不存在数据依赖性,处理器可以改变语句对应的机器指令的执行顺序

内存系统的重排序:由于处理器使用缓存和读 / 写缓冲区,这使得加载和储存操作看上去可能是在乱序执行

对于编译器,JMM的编译器重排序规则会禁止特定类型的编译器重排序,【不是所有的编译器重排序都要禁止】

对于处理器重排序,JMM的处理器重排序规则会要求java编译器生成指令时插入特定类型的内存屏障【memory barriers】指令。通过内存屏障指令来禁止特定类型的处理器重排序,为程序员提供一致的内存可见性保证

JVM规范了视图定义一种JMM来屏蔽各个硬件平台和OS的内存访问差异,属于语言级的内存模型,实现让Java程序在各平台下都能达到一致的内存访问效果,通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致的内存可见性保证。

内存屏障

为了实现volatile可见性和happen-before的语义,JVM底层通过一个叫做“内存屏障”的东西来完成。内存屏障,也叫做内存栅栏,是一组处理器指令,用于实现对内存的顺序限制,

是否可以重排序 第二个操作
第一个操作 普通读 / 写  volatile volatile写
普通读 / 写      
volatile读 no no no
volatile写   no no

当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。

当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。

当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。

为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能。为此,JMM采取保守策略。下面是基于保守策略的JMM内存屏障插入策略。

JMM:JMM属于语言级的内存模型,它确保在不同的编译器和不同的处理器平台上,通过禁止特定类型的编译器重排序和处理器冲排序,为程序员提供了一致的内存可见性保证

 

并发编程系列(六)volatile 之 as-if-serial 指令重排 volatile内存语义 volatile原理

 

volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况忽略不必要的屏障。在JMM基础中就有提到过各个处理器对各个屏障的支持度,其中x86处理器仅会对写-读操作做重排序。

原理

volatile主要作用是具有可见性和原子性(单个变量),其实现原理就是利用屏障来保障实现。

要想彻底掌握就应该多做下相关场景的编码,经典的场景有:状态标记量、volatile方式的double check等。单例的双重检查


推荐阅读
author-avatar
手机用户2502921877
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有