Java volatile如何实现禁止指令重排

计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为以下三种:

源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 -> 最终执行指令

单线程环境里面确保最终执行结果和代码顺序的结果一致

处理器在进行重排序时,必须要考虑指令之间的数据依赖性

多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。

指令重排 - example 1

public void mySort() {
	int x = 11;
	int y = 12;
	x = x + 5;
	y = x * x;
}

按照正常单线程环境,执行顺序是 1 2 3 4

但是在多线程环境下,可能出现以下的顺序:

2 1 3 4

1 3 2 4

上述的过程就可以当做是指令的重排,即内部执行顺序,和我们的代码顺序不一样

但是指令重排也是有限制的,即不会出现下面的顺序

4 3 2 1

因为处理器在进行重排时候,必须考虑到指令之间的数据依赖性

因为步骤 4:需要依赖于 y的申明,以及x的申明,故因为存在数据依赖,无法首先执行

例子

int a,b,x,y = 0

线程1 线程2
x = a; y = b;
b = 1; a = 2;
x = 0; y = 0

因为上面的代码,不存在数据的依赖性,因此编译器可能对数据进行重排

线程1 线程2
b = 1; a = 2;
x = a; y = b;
x = 2; y = 1

这样造成的结果,和最开始的就不一致了,这就是导致重排后,结果和最开始的不一样,因此为了防止这种结果出现,volatile就规定禁止指令重排,为了保证数据的一致性

指令重排 - example 2

比如下面这段代码

public class ResortSeqDemo {
  int a= 0;
  boolean flag = false;

  public void method01() {
    a = 1;
    flag = true;
  }

  public void method02() {
    if(flag) {
      a = a + 5;
      System.out.println("reValue:" + a);
    }
  }
}

我们按照正常的顺序,分别调用method01() 和 method02() 那么,最终输出就是 a = 6

但是如果在多线程环境下,因为方法1 和 方法2,他们之间不能存在数据依赖的问题,因此原先的顺序可能是

a = 1;
flag = true;

a = a + 5;
System.out.println("reValue:" + a);

但是在经过编译器,指令,或者内存的重排后,可能会出现这样的情况

flag = true;

a = a + 5;
System.out.println("reValue:" + a);

a = 1;

也就是先执行 flag = true后,另外一个线程马上调用方法2,满足 flag的判断,最终让a + 5,结果为5,这样同样出现了数据不一致的问题

为什么会出现这个结果:多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。

这样就需要通过volatile来修饰,来保证线程安全性

Volatile针对指令重排做了啥

Volatile实现禁止指令重排优化,从而避免了多线程环境下程序出现乱序执行的现象

首先了解一个概念,内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:

保证特定操作的顺序保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)

由于编译器和处理器都能执行指令重排的优化,如果在指令键插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说,通过插入内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是刷新出各种CPU的缓存数,因此任何cpu上的线程都能读取到这些数据的最新版本

也就是在Volatile的写和读的时候,加入屏障,防止出现指令重排线程安全得到保证

工作内存与主内存同步延迟现象导致的可见性问题

  • 可以使用synchronized或volatile关键字解决,它们都可以使得一个线程修改后的变量立即对其他线程可见。
  • 对于指令重排导致的可见性问题和有序性问题
  • 可以利用volatile关键字解决,因为volatile的另一个作用就是禁止重排序优化。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 深入了解Java中Volatile关键字

    一.基本概念 先补充一下概念:Java 内存模型中的可见性.原子性和有序性. 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情.为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制. 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的.也就是一个线程修改的结果.另一个线程马上就能看到.比如:用volatile修饰的变量,就会具有可见性.volatile修饰

  • Java并发volatile可见性的验证实现

    普通读 无法及时获得 主内存变量 public class volatileTest { static boolean flag = false;//非volatile变量 public static void main(String[] args) throws Exception { new Thread(new Runnable() { @Override public void run() { while(!flag){ }; } }).start(); Thread.sleep(100

  • Java并发编程——volatile关键字

    一.volatile是什么 volatile是Java并发编程中重要的一个关键字,被比喻为"轻量级的synchronized",与synchronized不同的是,volatile只能修饰变量,无法修饰方法及代码块等. 下面是使用volatile关键字实现的单例模式: public class Singleton implements Serializable { private static volatile Singleton singleton; private Singleto

  • 详细分析java并发之volatile关键字

    Java面试中经常会涉及关于volatile的问题.本文梳理下volatile关键知识点. volatile字意为"易失性",在Java中用做修饰对象变量.它不是Java特有,在C,C++,C#等编程语言也存在,只是在其它编程语言中使用有所差异,但总体语义一致.比如使用volatile 能阻止编译器对变量的读写优化.简单说,如果一个变量被修饰为volatile,相当于告诉系统说我容易变化,编译器你不要随便优化(重排序,缓存)我. Happens-before 规范上,Java内存模型遵

  • Java Volatile关键字实现原理过程解析

    volatile的用法 volatile通常被比喻成"轻量级的synchronized",也是Java并发编程中比较重要的一个关键字.和synchronized不同,volatile是一个变量修饰符,只能用来修饰变量.无法修饰方法及代码块等. volatile的用法比较简单,只需要在声明一个可能被多线程同时访问的变量时,使用volatile修饰就可以了. 如以下代码,是一个比较典型的使用双重锁校验的形式实现单例的,其中使用volatile关键字修饰可能被多个线程同时访问到的single

  • Java多线程volatile原理及用法解析

    首先volatile有两大功能: 保证线程可见性 禁止指令重排序 1.保证线程可见性 首先我们来看这样一个程序,其中不加volatile关键字运行的结果截然不同,加上volatile程序能够正常结束,不加则程序进入死循环: package com.designmodal.design.juc01; import java.util.concurrent.TimeUnit; /** * @author D-L * @Classname T001_volatile * @Version 1.0 *

  • Java Volatile关键字同步机制详解

    Volatile关键字--最轻量级的同步机制1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的.(实现可见性) 例如:如果一个oldvalue -->修改为newvalue ,这时的newvalue可以被其他的线程看到. 2.volatile不是线程安全的,只能保证对单次读/写的原子性.i++ 这种操作不能保证原子性.(不能保证原子性)最常使用场景:一写多读代码演示Volatile的可见性 public class VolatileCa

  • Java并发编程volatile关键字的作用

    日常编程中出现 volatile 关键字的频率并不高,大家可能对 volatile 关键字比较陌生,再深入一点也许是听闻 volatile 只能保证可见性而不能保证原子性,无法有效保证线程安全,于是更加避免使用 volatile ,简简单单加上synchronize关键字就完事了.本文稍微深入探讨 volatile 关键字,分析其作用及对应的使用场景. 并发编程的几个概念简述 首先简单介绍几个与并发编程相关的概念: 可见性 可见性是指变量在线程之间是否可见,JVM 中默认情况下线程之间不具备可见

  • Java volatile如何实现禁止指令重排

    计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令重排,一般分为以下三种: 源代码 -> 编译器优化的重排 -> 指令并行的重排 -> 内存系统的重排 -> 最终执行指令 单线程环境里面确保最终执行结果和代码顺序的结果一致 处理器在进行重排序时,必须要考虑指令之间的数据依赖性 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测. 指令重排 - example 1 public void mySort() { i

  • Java中volatile防止指令重排

    目录 什么是指令重排? 为什么指令重排能够提高性能 volatile是怎么禁止指令重排的? volatile可以防止指令重排,在多线程环境下有时候我们需要使用volatile来防止指令重排,来保证代码运行后数据的准确性 什么是指令重排? 计算机在执行程序时,为了提高性能,编译器和处理器一般会进行指令重排,一般分为以下三种: 指令重排有以下三个特点: 1.单线程环境下指令重排后可以保证与顺序执行指令的结果一致(就是不进行指令重排的情况) //原来的执行顺序 a=1; b=0; //进行指令重排后执

  • 详解Java volatile 内存屏障底层原理语义

    目录 一.volatile关键字介绍及底层原理 1.volatile的特性(内存语义) 2.volatile底层原理 二.volatile--可见性 三.volatile--无法保证原子性 四.volatile--禁止指令重排 1.指令重排 2.as-if-serial语义 五.volatile与内存屏障(Memory Barrier) 1.内存屏障(Memory Barrier) 2.volatile的内存语义实现 六.JMM对volatile的特殊规则定义 一.volatile关键字介绍及底

  • Java指令重排在多线程环境下的解决方式

    目录 一.序言 二.问题复原 (一)关联变量 1.结果预测 2.指令重排 (二)new创建对象 1.解析创建过程 2.重排序过程分析 三.应对指令重排 (一)AtomicReference原子类 (二)volatile关键字 四.指令重排的理解 1.指令重排广泛存在 2.多线程环境指令重排 3.synchronized锁与重排序无关 一.序言 指令重排在单线程环境下有利于提高程序的执行效率,不会对程序产生负面影响:在多线程环境下,指令重排会给程序带来意想不到的错误. 本文对多线程指令重排问题进行

  • Java Volatile应用单例模式实现过程解析

    单例模式 回顾一下,单线程下的单例模式代码 饿汉式 构造器私有化 自行创建,并且用静态变量保存static 向外提供这个实例 public 强调这是一个单例,用final public class sington(){ public final static INSTANCE = new singleton(); private singleton(){} } 第二种:jdk1.5之后用枚举类型 枚举类型:表示该类型的对象是有限的几个 我们可以限定为1个,就称了单例 public enum Si

  • java Volatile与Synchronized的区别

    引言 在研究并发程序时,我们可能都知道volatile和synchronized是用于多线程中,用于线程安全和变量可见性的,但是具体两者怎么使用,有何区别可能还是稀里糊涂一知半解,在此就自己简单的理解总结一下二者的区别,和大家一块儿学习!我们需要了解java中关键字volatile和synchronized关键字的使用以及lock类的用法. 首先,了解下java的内存模型: java的线程内存模型中定义了每个线程都有一份自己的共享变量副本(本地内存),里面存放自己私有的数据,其他线程不能直接访问

  • 关于指令重排现象的两个阶段详解

    目录 编译期指令重排 1.上神秘代码 2.编译成Java字节码(没加volatile) 3.编译成Java字节码(加了volatile) 4.编译器优化 运行期指令重排 那什么时候会产生指令重排现象呢?两个阶段:1.编译期:2.运行期. 编译期指令重排 解释型语言是在运行期间执行编译+运行动作,所以运行效率较编译型语言低.Java既可以作为解释型语言去用,也可以作为编译型语言.但是主流的做法是当成编译型语言在用.那Java在编译期做了指令重排优化吗?做了哪些优化?能不能让我看看?为了满足大家的好

  • Java Volatile 变量详解及使用方法

    Java Volatile 详解 概要: Java 语言中的 Volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分.本文介绍了几种有效使用 volatile 变量的模式,并强调了几种不适合使用 volatile 变量的情形. 锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(

  • 为什么Java volatile++不是原子性的详解

    问题 在讨论原子性操作时,我们经常会听到一个说法:任意单个volatile变量的读写具有原子性,但是volatile++这种操作除外. 所以问题就是:为什么volatile++不是原子性的? 答案 因为它实际上是三个操作组成的一个符合操作. 首先获取volatile变量的值 将该变量的值加1 将该volatile变量的值写会到对应的主存地址 一个很简单的例子: 如果两个线程在volatile读阶段都拿到的是a=1,那么后续在线程对应的CPU核心上进行自增当然都得到的是a=2,最后两个写操作不管怎

随机推荐