首页 > 其他分享 >volatile是如何保证有序性的?

volatile是如何保证有序性的?

时间:2023-07-02 17:57:20浏览次数:34  
标签:happens volatile flag 保证 有序性 线程 排序 before

为什么需要保证有序性?

有如下代码,在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.

标签:happens,volatile,flag,保证,有序性,线程,排序,before
From: https://www.cnblogs.com/yuyiming/p/17520955.html

相关文章

  • 众所周知,梯度下降法是一种基本的优化算法,不能保证全局最优,也不能保证效率。为什么它仍
    梯度下降法在深度学习中被广泛应用的原因主要有以下几点:适用性广泛:梯度下降法可以应用于各种深度学习模型,包括神经网络、卷积神经网络、循环神经网络等。而传统的凸优化算法和粒子群算法往往只适用于特定类型的优化问题。原理简单:梯度下降法的原理相对简单,易于理解和实现。......
  • volatile关键字的作用
    Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。volatile变量具备两种特性,volatile变量不会被缓存在寄存器或者对其他的处理不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。变量可见性:其一是保证该变量对所有线程可见......
  • 如何保证安全系统的安全性?
    嵌入式系统是当今安全技术的关键嵌入式系统是一种在大型设备内执行特定任务的专用计算机系统。它们被设计成小型且高效,同时极其可靠的控制器,能够执行原本需要复杂和昂贵解决方案的任务。嵌入式控制器广泛应用于航空航天、能源、交通、安全系统、消费电子和医疗诊断设备等多个行......
  • 保证线程安全的10个小技巧
    前言对于从事后端开发的同学来说,线程安全问题是我们每天都需要考虑的问题。线程安全问题通俗的讲:主要是在多线程的环境下,不同线程同时读和写公共资源(临界资源),导致的数据异常问题。比如:变量a=0,线程1给该变量+1,线程2也给该变量+1。此时,线程3获取a的值有可能不是2,而是1。线程3这不......
  • volatile 关键字
    原文链接:https://liamw.cn/albums/csharp-dotnet/10-understand-the-volatile-keyword要理解C#中的 volatile 关键字,就要先知道编译器背后的一个基本优化原理。比如对于下面这段代码: publicclassExample{publicintx;publicvoidDoWork(){x......
  • 怎么保证接口安全
    1.首先应该考虑使用https协议,因为http协议是不安全的,一般来说购买服务器的时候厂商都会送免费的https的ssl证书,只需要在nginx配置就可以了。2.接口应该开启加密,分为对称加密和非对称加密3.对称加密:客户端和服务端使用同一个秘钥4.非对称加密:5.数据验签,避免黑客通过抓包......
  • 【Android】如何实现同一个布局保证高度不变,使用不同高度的背景
    背景预实现一个切换tab,实现选中与未选中的背景切换,特别之处在于选中背景图和未选中背景图高度不相同,切换之后需要在java代码中动态设置LayoutParams改变高度。预期效果当前问题点选中背景为.9图,未选中背景为xml中通过shape实现。将当前ViewGroup设置为选中状态的固定高度选中效果正......
  • 保证editView大小不变防止输入过多变形以及TextView的style引用
    <TableLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="fill_parent"android:layout_height="fill_parent"android:stretchColumns="1"android:padding="5dip">......
  • 基于标准的自动化测试:保证质量的必备方法
    随着软件行业的不断发展,我们对于软件质量的要求也越来越高。而在保证软件质量的过程中,自动化测试已成为了一项必备的方法。那么,什么是基于标准的自动化测试?它为什么能够成为保证质量的必备方法呢?下面就为大家一一解释。1.什么是基于标准的自动化测试?基于标准的自动化测试指的是在遵......
  • 主从架构如何保证读写一致性(主从网络延迟)
    问题在高并发的场景下,一般是读写分离,写主库,读从库。但是主从同步存在延迟,原因可能有a.主库的从库太多b.从库硬件配置比主库差c.慢SQL语句过多d.主从库之间的网络延迟e.主库读写压力大如果数据写入主库之后还未来得及同步到从库,此时读从库就会读到脏数据解决方案1......