为什么需要保证有序性?

有如下代码,在int i = a;执行了的情况下,i的值最终会为几?

public class NoVolatileExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1;
flag = true;
}
public void reader() {
if (flag) {
int i = a;
}
}
}

假设现在有线程A首先执行writer()方法,然后线程B执行reader()方法,那么线程A和B执行完毕之后i一定会为1?答案是不一定,根据as-if-serial原则,对与单线程执行程序,如果执行的结果不会改变,允许重排序。那么就可能会出现以下的情况。

  • 线程A 执行flag = true
  • 线程B 执行if (flag)
  • 线程B 执行int i = a;
  • 线程A 执行a = 1;

最终结果 i = 0;和我们认为的执行结果不一样。

内存屏障介绍

为了性能优化,JVM会在不改变数据依赖性的情况下,允许编译器和处理器对指令序列进行重排序,而有序性问题指的就是程序代码执行的顺序与程序员编写程序的顺序不一致,导致程序结果不正确的问题。而加了volatile修饰的共享变量,则通过内存屏障解决了多线程下有序性问题。

内存屏障分为以下四种

volatile 内存语义的实现

下面来看看 JMM 如何实现 volatile 写/读的内存语义。为了实现 volatile 内存语义,JMM 会分别限制这两种类型的重排序类型。下表是 JMM 针对编译器制定的 volatile 重排序规则表。



举例来说,第三行最后一个单元格的意思是:在程序中,当第一个操作为普通变量的读或写时,如果第二个操作为 volatile 写,则编译器不能重排序这两个操作。

从上表我们可以看出。

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

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

  • 在每个 volatile 写操作的前面插入一个 StoreStore 屏障。
  • 在每个 volatile 写操作的后面插入一个 StoreLoad 屏障。
  • 在每个 volatile 读操作的后面插入一个 LoadLoad 屏障。
  • 在每个 volatile 读操作的后面插入一个 LoadStore 屏障。

针对以上代码如何保证有序性?

对flag 添加volatile修饰

public class VolatileExample {
int a = 0;
volatile boolean flag = false;
public void writer() {
a = 1; // 1
flag = true; //2
}
public void reader() {
if (flag) { //3
int i = a; //4
}
}
}



根据 happens-before规则(不了解的可以先去了解一下happens-before原则),这个过程建立的 happens-before 关系可以分为 3 类:

根据程序次序规则,1 happens-before 2;3 happens-before 4。

根据 volatile 规则,2 happens-before 3。

根据 happens-before 的传递性规则,1 happens-before 4。

所有对于线程A对a的写入(a=1),b线程在执行4的时候是可见的,最终结果i=1.

volatile是如何保证有序性的?的更多相关文章

  1. 为什么volatile能保证有序性不能保证原子性

    对于内存模型的三大特性:有序性.原子性.可见性. 大家都知道volatile能保证可见性和有序性但是不能保证原子性,但是为什么呢? 一.原子性.有序性.可见性 1.原子性: (1)原子的意思代表着-- ...

  2. volatile并不能保证数据同步、只能保证读取到最新主内存数据

    在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配.其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈, 线程栈保存了线程运行时候变量值信息.当线程访问某一个对象时候值的 ...

  3. volatile变量能保证线程安全性吗?为什么?

    在谈及线程安全时,常会说到一个变量——volatile.在<Java并发编程实战>一书中是这么定义volatile的——Java语言提供了一种稍弱的同步机制,即volatile变量,用来确 ...

  4. Java 并发编程:volatile的使用及其原理

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  5. Java 开发, volatile 你必须了解一下

    上一篇文章说了 CAS 原理,其中说到了 Atomic* 类,他们实现原子操作的机制就依靠了 volatile 的内存可见性特性.如果还不了解 CAS 和 Atomic*,建议看一下我们说的 CAS ...

  6. Java多线程之三volatile与等待通知机制示例

    原子性,可见性与有序性 在多线程中,线程同步的时候一般需要考虑原子性,可见性与有序性 原子性 原子性定义:一个操作或者多个操作在执行过程中要么全部执行完成,要么全部都不执行,不存在执行一部分的情况. ...

  7. 【Java并发编程】11、volatile的使用及其原理

    一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果 ...

  8. 深入理解Java中的volatile关键字

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  9. volatile的用法

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  10. 【转】Java 并发编程:volatile的使用及其原理

    一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果 ...

随机推荐

  1. 从原理聊JVM(一):染色标记和垃圾回收算法

    作者:京东科技 康志兴 1 JVM运行时内存划分 1.1 运行时数据区域 • 方法区 属于共享内存区域,存储已被虚拟机加载的类信息.常量.静态变量.即时编译器编译后的代码等数据.运行时常量池,属于方法 ...

  2. canvas-绘制3D金字塔

    var canvas1 = document.getElementById("canvas1"); var context = canvas1.getContext("2 ...

  3. Rsync文件同步及备份

    Rsync文件同步及备份 目录 Rsync文件同步及备份 Rsync基本概述 远程文件传输 服务端口 Rsync的三种传输模式 本地方式(类似cp) 远程方式(类似scp) 守护进程(C/S结构) R ...

  4. 音视频八股文(9)-- flv的h264六层结构和aac六层结构

    flv介绍 FLV(Flash Video)是Adobe公司推出的⼀种流媒体格式,由于其封装后的⾳视频⽂件体积⼩.封装简单等特点,⾮常适合于互联⽹上使⽤.⽬前主流的视频⽹站基本都⽀持FLV.采⽤FLV ...

  5. 2023-04-19:给定一个非负数组arr 任何两个数差值的绝对值,如果arr中没有,都要加入到arr里 然后新的arr继续,任何两个数差值的绝对值,如果arr中没有,都要加入到arr里 一直到ar

    2023-04-19:给定一个非负数组arr 任何两个数差值的绝对值,如果arr中没有,都要加入到arr里 然后新的arr继续,任何两个数差值的绝对值,如果arr中没有,都要加入到arr里 一直到ar ...

  6. 2021-07-15:接雨水 II。给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。

    2021-07-15:接雨水 II.给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水. 福大大 答案2021-07-15: 小根堆 ...

  7. Windows server 2012 安装ad域

    Windows server 2012 安装ad域   安装ad域(active directory)服务的作用:存储目录数据并管理域之间的通信,包括用户登录处理,身份验证和目录搜索等. 1.使用ad ...

  8. 用go设计开发一个自己的轻量级登录库/框架吧(拓展篇)

    给自己的库/框架拓展一下吧(拓展篇) 主库:weloe/token-go: a light login library. 扩展库:weloe/token-go-extensions (github.c ...

  9. 代码随想录算法训练营Day31 贪心算法| 理论基础 455.分发饼干 376. 摆动序列 53. 最大子序和

    代码随想录算法训练营 理论基础 什么是贪心 贪心的本质是选择每一阶段的局部最优,从而达到全局最优. 每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优. 贪心的套路(什么时候用贪心) 贪心 ...

  10. 一天吃透Spring面试八股文

    内容摘自我的学习网站:topjavaer.cn Spring是一个轻量级的开源开发框架,主要用于管理 Java 应用程序中的组件和对象,并提供各种服务,如事务管理.安全控制.面向切面编程和远程访问等. ...