广告
返回顶部
首页 > 资讯 > 后端开发 > Python >并发编程之Java内存模型volatile的内存语义
  • 161
分享到

并发编程之Java内存模型volatile的内存语义

2024-04-02 19:04:59 161人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

1、volatile的特性 理解volatile特性的一个好办法是把对volatile变量的单个读/写,看成是使用同一个锁对单个读/写操作做了同步。 代码示例: package

1、volatile的特性

理解volatile特性的一个好办法是把对volatile变量的单个读/写,看成是使用同一个对单个读/写操作做了同步。

代码示例:


package com.lizba.p1;


public class VolatileFeatureExample {

    
    volatile long v1 = 0l;

    
    public void set(long l) {
        v1 = l;
    }

    
    public void getAndIncrement() {
        v1++;
    }

    
    public long get() {
        return v1;
    }

}

假设有多个线程分别调用上面程序的3个方法,这个程序在语义上和下面程序等价。


package com.lizba.p1;


public class SynFeatureExample {

    
    long v1 = 0L;

    
    public synchronized void set(long l) {
        v1 = l;
    }


    
    public void getAndIncrement() {
        long temp = get();
        // v1加一
        temp += 1L;
        set(temp);
    }

    
    public synchronized long get() {
        return v1;
    }

}

如上两个程序所示,一个volatile变量的单个读\写操作,与一个普通变量的读\写操作都是使用同一个锁来同步,它们之间的执行效果相同。

上述代码总结:

锁的happens-before规则保证释放锁和获取锁的两个线程之间的内存可见性,这意味着对一个volatile变量的读,总能看到(任意线程)对这个volatile变量最后的写入。
锁的语义决定了临界区代码的执行具有原子性。这意味着,即使是64位的long型和double型变量,只要它是volatile变量,对该变量的读/写就具有原子性。如果是多个volatile操作或类似于volatile++这种复合操作,这些操作整体上不具备原子性。

总结volatile特性:

  • 可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
  • 原子性。对任意volatile变量的读/写具有原子性,但类似volatile++这种复合操作不具有原子性。

2、volatile写-读建立的happens-before关系

  • 对于程序员来说,我们更加需要关注的是volatile对线程内存的可见性。

jdk1.5(jsR-133)开始,volatile变量的写-读可以实现线程之间的通信。从内存语义的角度来说,volatile的写-读与锁的释放-获取有相同的内存效果。

  • volatile的写和锁的释放有相同的内存语义
  • volatile的读和锁的获取有相同的内存语义

代码示例:


package com.lizba.p1;


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
            System.out.println(i);
        }
    }
    
}

假设线程A执行writer()方法之后,线程B执行reader()方法。根据happens-before规则,

这个过程建立的happens-before关系如下:

  • 根据程序次序规则,1 happens-before 23 happens-before 4
  • 根据volatile规则,2 happens-before 3
  • 根据happens-before的传递性规则,1 happens-before 4

图示上述happens-before关系:

总结:这里A线程写一个volatile变量后,B线程读同一个volatile变量。A线程在写volatile变量之前所有可见的共享变量,在B线程读同一个volatile变量后,将立即对B线程可见。

3、volatile写-读的内存语义

volatile写的内存语义

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。

以上面的VolatileExample为例,假设A线程首先执行writer()方法,随后线程B执行reader()方法,初始时两个线程的本地内存中的flag和a都是初始状态。

A执行volatile写后,共享变量状态示意图。

线程A在写flag变量后,本地内存A中被线程A更新过的两个共享变量的值被刷新到主内存中,此时A的本地内存和主内存中的值是一致的。

volatile读的内存语义

当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将会从主内存中读取共享变量。

B执行volatile读后,共享变量的状态示意图:

在读flag变量后,本地内存B包含的值已经被置为无效。此时,线程B必须从主内存中重新读取共享变量。线程B的读取操作将导致本地内存B与主内存中的共享变量的值变为一致。

总结volatile的写和volatile读的内存语义

  • 线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所做修改的)消息。
  • 线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改的)消息。
  • 线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过主内存向线程B发送消息。

4、volatile内存语义实现

程序的重排序分为编译器重排序和处理器重排序(我的前面的博文内容有写哈)。为了实现volatile内存语义,JMM会分别禁止这两种类型的重排序。

volatile重排序规则表:

是否能重排序 第二个操作
第一个操作 普通读/写 volatile读 volatile写
普通读/写 NO
volatile读 NO NO NO
volatile写 NO NO

上图举例:第一行最后一个单元格意思是,在程序中第一个操作为普通读/写时,如果第二个操作为volatile写,则编译器不能重排序。

总结上图:

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

为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。

JMM采取的是保守策略内存屏障插入策略,如下:

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

保守策略可以保证在任意处理器平台上,任意程序中都能得到正确的volatile内存语义。

保守策略下,volatile写插入内存屏障后生成的指令序列图:

解释:

StoreStore屏障可以保证在volatile写之前,其前面所有普通写操作已经对任意处理器可见了。这是因为StoreStore屏障将保障上面所有普通写在volatile写之前刷新到主内存。

保守策略下,volatile读插入内存屏障后生成的指令序列图:

解释:

LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。

上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。

代码示例:


package com.lizba.p1;


public class VolatileBarrierExample {

    int a;
    volatile int v1 = 1;
    volatile int v2 = 2;

    void readAndWrite() {
        // 第一个volatile读
        int i = v1;
        // 第二个volatile读
        int j = v2;
        // 普通写
        a = i + j;
        // 第一个volatile写
        v1 = i + 1;
        // 第二个volatile写
        v2 = j * 2;
    }

    // ... 其他方法

}

针对VolatileBarrierExample的readAndWrite(),编译器生成字节码时可以做如下优化:

注意:最后的StoreLoad屏障无法省略。因为第二个volatile写之后,程序return。此时编译器无法准确断定后面是否会有volatile读写操作,为了安全起见,编译器通常会在这里插入一个StoreLoad屏障。

上面的优化可以针对任意处理器平台,但是由于不同的处理器有不同的“松紧度”的处理器内存模型,内存屏障的插入还可以根据具体的处理器内存模型继续优化。

X86处理器平台优化

X86处理器仅会对写-读操作做重排序。X86不会对读-读、读-写和写-写重排序,因此X86处理器会省略掉这3种操作类型对应的内存屏障。在X86平台中,JMM仅需要在volatile写后插入一个StoreLoad屏障即可正确实现volatile写-读内存语义。同时这样意味着X86处理器中,volatile写的开销会远远大于读的开销。

5、volatile和锁的比较

功能上:

锁比volatile更强大

可伸缩性和执行性能上:

volatile更具有优势

到此这篇关于并发编程之Java内存模型volatile的内存语义的文章就介绍到这了,更多相关Java内存模型volatile的内存语义内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 并发编程之Java内存模型volatile的内存语义

本文链接: https://www.lsjlt.com/news/156380.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 并发编程之Java内存模型volatile的内存语义
    1、volatile的特性 理解volatile特性的一个好办法是把对volatile变量的单个读/写,看成是使用同一个锁对单个读/写操作做了同步。 代码示例: package ...
    99+
    2022-11-12
  • 并发编程之Java内存模型锁的内存语义
    目录1、锁的释放-获取建立的happens-before关系2、锁释放和获取的内存语义3、锁内存的语义实现4、concurrent包的实现简介: 锁的作用是让临界区互斥执行。本文阐述...
    99+
    2022-11-12
  • Java并发编程之volatile与JMM多线程内存模型
    目录一、通过程序看现象二、为什么会产生这种现象(JMM模型)?三、MESI 缓存一致性协议 一、通过程序看现象 在开始为大家讲解Java 多线程缓存模型之前,我们先看下面的这一段代码...
    99+
    2022-11-13
  • Java内存模型volatile的内存语义是什么
    这篇文章主要介绍“Java内存模型volatile的内存语义是什么”,在日常操作中,相信很多人在Java内存模型volatile的内存语义是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java内存模型v...
    99+
    2023-06-25
  • 并发编程之Java内存模型
    目录一、Java内存模型的基础1.1 并发编程模型的两个关键问题1.2 Java内存模型的抽象结构1.3 从源代码到指令重排序1.4 写缓冲区和内存屏障1.4.1 写缓冲区1.4.2...
    99+
    2022-11-12
  • Java并发编程之Java内存模型
    目录1、什么是Java的内存模型2、为什么需要Java内存模型3、Java内存模型及操作规范4、Java内存模型规定的原子操作5、Java内存模型同步协议6、Java内存模型的HB法...
    99+
    2022-11-12
  • Java并发编程之volatile与JMM多线程内存模型实例分析
    本篇内容主要讲解“Java并发编程之volatile与JMM多线程内存模型实例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java并发编程之volatile与JMM多线程内存模型实例分析”...
    99+
    2023-06-30
  • java高并发的volatile与Java内存模型详解
    public class Demo09 { public static boolean flag = true; public static class T1...
    99+
    2022-11-12
  • Java内存模型final的内存语义
    目录1、final域的重排序规则final2、写final域的重排序规则3、读final与的重排序规则4、final域为引用类型5、为什么final引用不能从构造函数内“逸出”6、f...
    99+
    2022-11-12
  • java高并发的volatile与Java内存模型是什么
    这篇文章将为大家详细讲解有关java高并发的volatile与Java内存模型是什么,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。public class Demo09 {&...
    99+
    2023-06-25
  • 并发编程之Java内存模型顺序一致性
    目录1、数据竞争和顺序一致性1.1 Java内存模型规范对数据竞争的定义1.2 JMM对多线程程序的内存一致性做的保证2、顺序一致性内存模型2.1 特性2.2 举例说明顺序一致性模型...
    99+
    2022-11-12
  • Java并发中的内存模型
    这篇文章主要讲解了“Java并发中的内存模型”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java并发中的内存模型”吧!CPU和内存在讲JMM之前,我想先和大家聊聊硬件层面的东西。大家应该都...
    99+
    2023-06-02
  • Java并发编程中的内存模型是什么
    这篇文章主要介绍“Java并发编程中的内存模型是什么”,在日常操作中,相信很多人在Java并发编程中的内存模型是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java并发编程中的内存模型是什么”的疑惑有所...
    99+
    2023-06-25
  • Java并发内存模型详情
    目录1、Java内存模型2、硬件内存架构3、实际执行3.1 共享对象可见性3.2 竞争条件Java是一门支持多线程执行的语言,要编写正确的并发程序,了解Java内存模型是重要前提。而...
    99+
    2022-11-12
  • 怎么浅谈Java并发编程中的Java内存模型
    这篇文章的内容主要围绕怎么浅谈Java并发编程中的Java内存模型进行讲述,文章内容清晰易懂,条理清晰,非常适合新手学习,值得大家去阅读。感兴趣的朋友可以跟随小编一起阅读吧。希望大家通过这篇文章有所收获!物理计算机并发问题在介绍Java内存...
    99+
    2023-06-17
  • 深度理解Java中volatile的内存语义
    volatile可见性实验 举个栗子 我这里开了两个线程,后面的线程去修改volatile变量,前面的线程不断获取volatile变量, 结果是会一致卡在死循环,控制台没有任何输出...
    99+
    2022-11-12
  • Java内存模型的并发处理示例
    这篇文章给大家分享的是有关Java内存模型的并发处理示例的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。Java的优点是什么1. 简单,只需理解基本的概念,就可以编写适合于各种情况的应用程序;2. 面向对象;3. ...
    99+
    2023-06-14
  • Golang函数的内存模型和并发编程的关系和模式
    Golang函数的内存模型和并发编程的关系和模式Golang(Go)是一种新兴的编程语言,它以简单、高效和并发编程为特色。在Golang中,函数是一等公民,因此理解其内存模型对于正确使用和优化性能至关重要。随着计算机硬件的发展,多核和分布式...
    99+
    2023-05-16
    Golang 并发编程 内存模型
  • C语言的编程之美之内存函数
    目录内存函数memcpy内存拷贝原格式分析内存拷贝的问题memmove内存重叠拷贝C语言规定原格式分析memset内存设置原格式memcmp内存比较原格式总结内存函数 memcpy内...
    99+
    2022-11-12
  • Java内存模型之重排序的示例分析
    小编给大家分享一下Java内存模型之重排序的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!一、数据依赖性如果两个操作访问同一个变量,而且这两个操作中有一个操作为写操作,此时这两个操作之间存在数据依赖性。数据依赖性分...
    99+
    2023-06-15
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作