小熊回收站|分析:volatile内存屏障+实现原理(JMM和MESI)( 四 )
结合上图 , 假设初始时 , 这3个内存中的x值都为0 。 线程A在执行时 , 把更新后的x值(假设值为1)临时存放在自己的本地内存 A中 。 当线程A和线程B需要通信时 , 线程A首先会把自己本地内存中修改后的x值刷新到主内 存中 , 此时主内存中的x值变为了1 。 随后 , 线程B到主内存中去读取线程A更新后的x值 , 此时线程B的本地内存的x值也变为了1 。从整体来看 , 这两个步骤实质上是线程A在向线程B发送消息 , 而且这个通信过程必须要经过主内存 。 JMM通过控制主内存与每个线程的本地内存之间的交互 , 来为Java程序员提供内存可见性保证 。
编译器的指令重排序综合上面从硬件层面和JVM层面的分析 , 我们知道在执行程序时 , 为了提高性能 , 编译器和处理器常常会对指令做重排序 。 重排序分3种类型:1)编译器优化的重排序 。 编译器在不改变单线程程序语义的前提下 , 可以重新安排语句的执行顺序 。 2)指令级并行的重排序 。 现代处理器采用了指令级并行技术(Instruction-Level Parallelism , ILP)来将多条指令重叠执行 。 如果不存在数据依赖性 , 处理器可以改变语句对应机器指令的执行顺序 。 3)内存系统的重排序 。 由于处理器使用缓存和读/写缓冲区 , 这使得加载和存储操作看上去可能是在乱序执行 。 从Java源代码到最终实际执行的指令序列 , 会分别经历下面3种重排序 , 如下图:
其中2和3属于处理器重排序(前面硬件层面已经分析过了) 。 而这些重排序都可能会导致可见性问题(编译器和处理器在重排序时会遵守数据依赖性 , 编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序 , 编译器会遵守happens-before规则和as-if-serial语义) 。 对于编译器 , JMM的编译器重排序规则会禁止特定类型的编译器重排 序(不是所有的编译器重排序都要禁止) 。 对于处理器重排序 , JMM的处理器重排序规则会要求Java编译器在生成指令序列时 , 插入特定类型的内存屏障(Memory Barriers , Intel称之为Memory Fence)指令 , 通过内存屏障指令来禁止特定类型的处理器重排序 。 JMM属于语言级的内存模型 , 它确保在不同的编译器和不同的处理器平台之上 , 通过禁止特定类型的编译器重排序和处理器重排序 , 为程序员提供一致的内存可见性保证 。 正是因为volatile的这个特性 , 所以单例模式中可以通过volatile关键字来解决双重检查锁(DCL)写法中所存在的问题 。
JMM层面的内存屏障在JMM 中把内存屏障分为四类:
StoreLoad Barriers是一个“全能型”的屏障 , 它同时具有其他3个屏障的效果 。 现代的多数处理器大多支持该屏障(其他类型的屏障不一定被所有处理器支持) 。 执行该屏障开销会很昂贵 , 因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(Buffer Fully Flush) 。
happens-before规则happens-before表示的是前一个操作的结果对于后续操作是可见的 , 它是一种表达多个线程之间对于内存的可见性 。 所以我们可以认为在 JMM 中 , 如果一个操作执行的结果需要对另一个操作可见 , 那么这两个操作必须要存在happens-before关系 。 这两个操作可以是同一个线程 , 也可以是不同的线程 , 如果想详细了解happens-before规则 , 可以点击这里 。
总结并发编程中有三大特性:原子性、可见性、有序性 , volatile通过内存屏障禁止指令重排序 , 主要遵循以下三个规则:
推荐阅读
- 芯片|气候异常!东北半个月遭台风三连击,分析:短期长期都不是好事
- 板块|美元贬值一定提振美股? 华尔街分析师表示并不,但对个别板块有明显刺激作用
- 股票|美元贬值一定提振美股? 华尔街分析师表示并不,但对个别板块有明显刺激作用
- 映璇汽车工作室|终于知道它为啥难卖了,看完长城WEY内部技术团队做的竞品分析
- 解说小南风|枪火重生:从武器机制分析什么适合上手
- 游龙战神|2020年中国搜索引擎行业市场现状及发展前景分析
- 李霄鹏|盘点鲁能冠军主帅,分析李霄鹏差在哪里,解读鲁能绝无可能换帅
- 任正非称华为岗位没有年龄限制 2020通信设备制造行业现状及发展前景趋势分析研究报告
- 软银一月购入274亿科技股看涨期权 2020跨境电子商务行业现状及发展前景趋势分析研究报告
- 支付码3米外可被盗刷 2020三方支付行业现状及发展前景趋势分析研究报告
