首页 > 其他分享 >volatile如何防止指令重排和保证有序性

volatile如何防止指令重排和保证有序性

时间:2023-11-06 10:59:14浏览次数:35  
标签:语句 屏障 指令 volatile 内存 重排 有序性

在多线程的世界里,一共有三个问题:原子性问题、可见性问题、有序性问题。整个java并发体系也是围绕着如何解决这三个问题来设计的。volatile关键字也不例外,我们都知道它解决了可见性和有序性,但是不能保证原子性。这篇文章也主要基于其中一个特性,也就是研究一下volatile是如何保证有序性的。 一、有序性 1、有序性案例 有序性指的是:程序执行的顺序按照代码的先后顺序执行。我们可以先看一个被列举了一万次的代码:

按照我们自己常规的想法,顺序应该从上往下依次执行,但是真实情况是:jvm会在真正执行这段代码的时候进行优化,发生指令的重排序。因此不能保证语句1一定在语句2先执行。 2、数据依赖性 上面的例子,你还会发现这样一个特点,就算是发生了指令的重排序,但是最后的结果总是正确的。我们再举一个例子:

这种情况会发生指令重排序吗?显然不会,原因是处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令2必须用到指令1的结果,那么处理器一定保证指令1在指令2执行。 3、多线程问题 这种数据的依赖性在单线程环境下一点问题没有,因为总能保证数据的正确,但是在多线程环境下就会出现错误。我们再举一个例子:

上面的这段代码由于语句1和语句2没有数据依赖性,因此会发生指令重排。do2只要看到flag为true,就执行。因此可能的顺序是: (1)语句1先于语句2:语句2->语句3->语句1->语句4。这时候的结果i=1。 (1)语句2先于语句1:语句2->语句3->语句4->语句1。这时候的结果i=0。 现在我们可以看到在多线程环境下如果发生了指令的重排序,会对结果造成影响。 上面一开始提到过,volatile可以保证有序性,也就是可以防止指令重排序。那么它是如何解决的呢?这就是内存屏障。因此我们从内存屏障讲起。 二、内存屏障 1、什么是内存屏障 内存屏障其实就是一个CPU指令,在硬件层面上来说可以扥为两种:Load Barrier 和 Store Barrier即读屏障和写屏障。主要有两个作用: (1)阻止屏障两侧的指令重排序; (2)强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。 在JVM层面上来说作用与上面的一样,但是种类可以分为四种:

2、volatile如何保证有序性? 首先一个变量被volatile关键字修饰之后有两个作用: (1)对于写操作:对变量更改完之后,要立刻写回到主存中。 (2)对于读操作:对变量读取的时候,要从主存中读,而不是缓存。 OK,现在针对上面JVM的四种内存屏障,应用到volatile身上。因此volatile也带有了这种效果。其实上面提到的这些内存屏障应用的效果,可以happen-before来总结归纳。 3、内存屏障分类 内存屏障有三种类型和一种伪类型: (1)lfence:即读屏障(Load Barrier),在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据,以保证读取的是最新的数据。 (2)sfence:即写屏障(Store Barrier),在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存,以保证写入的数据立刻对其他线程可见。 (3)mfence,即全能屏障,具备ifence和sfence的能力。 (4)Lock前缀:Lock不是一种内存屏障,但是它能完成类似全能型内存屏障的功能。 为什么说Lock是一种伪类型的内存屏障,是因为内存屏障具有happen-before的效果,而Lock在一定程度上保证了先后执行的顺序,因此也叫做伪类型。比如,IO操作的指令,当指令不执行时,就具有了mfence的功能。 OK,一句话说完就是内存屏障保证了volatile的有序性。在很多平台也看到了从计算机底层角度来分析的,要是详细写,不是一两篇就能完成的。

标签:语句,屏障,指令,volatile,内存,重排,有序性
From: https://www.cnblogs.com/cdlyy/p/17812053.html

相关文章

  • 面试10000次依然会问的【volatile】,你还不会?
    volatile关键字的定义volatile是Java语言提供的一种轻量级的同步机制,主要用于「确保变量的修改对其他线程是立即可见的」,以及「防止指令重排序」。使用volatile修饰的变量,其读写操作直接作用于主存,而不是线程的工作内存。这意味着一旦一个线程修改了volatile变量的值,其他线程立即......
  • 详述Java内存屏障,透彻理解volatile
    一般来说内存屏障分为两层:编译器屏障和CPU屏障,前者只在编译期生效,目的是防止编译器生成乱序的内存访问指令;后者通过插入或修改特定的CPU指令,在运行时防止内存访问指令乱序执行。下面简单说一下这两种屏障。1、编译器屏障编译器屏障如下:asmvolatile("":::"memory")内联汇......
  • Java Volatile和synchronized的区别,notify()和notifyAll()的区别
    JavaVolatile和synchronized的区别,notify()和notifyAll()的区别1.Volatile和synchronized的区别:(1)、volatile只能作用于变量,使用范围较小。synchronized可以用在变量、方法、类、同步代码块等,使用范围比较广。(2)、volatile只能保证可见性和有序性,不能保证原子性。......
  • MESI缓存一致性协议以及Volatile
    MESI(Modified,Exclusive,Shared,Invalid)是一种缓存一致性协议,用于解决多处理器系统中,多个处理器对同一块内存的并发读写可能导致的数据不一致性问题。MESI协议维护了每个缓存行的四种状态:Modified(M):表示缓存行已被修改,并且是唯一的拷贝。当其他处理器需要读取该数据时,必须先......
  • volatile介绍
        ......
  • volatile关键字
    volatile是一个类型修饰符(typespecifier)。它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。1.原理作用Volatile意思是“易变的”,应该解释为“直接存取原始内存地址”比较合适。“......
  • 为什么懒汉式单例模式要加volatile修饰符
    publicclassLazySingleton{privateLazySingleton(){}privatevolatilestaticLazySingletoninstance;publicsynchronizedstaticLazySingletongetInstance(){if(instance==null){instance=newLazySi......
  • volatile关键字和CAS的原子性操作
    volatile关键字volatile是Java中的关键字,用于修饰变量。它的作用是确保对被修饰变量的读写操作具有可见性和顺序性。可见性:当一个线程修改了volatile变量的值,其他线程可以立即看到最新的值。这是因为volatile变量在修改时会强制将最新的值刷新到主内存中,并在读取时从主......
  • 讲讲volatile的作用
    一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份(因为读取寄存器比读内存要快的多,所以会优化。)。下面是volatile变......
  • volatile关键字,使变量在多个线程之间可见
    在多线程中继承Thread类和实现runnable接口的别并不大,但是,如果是需要继承Thread之外的其他接口,就可以使用实现runnable接口的方式。测试类:publicclassPrintString{privatebooleanisContinuePrint=true;publicbooleanisContinuePrint(){returnis......