JUC多并发编程 volatile

news2024/9/22 2:18:15

特点:

  • 可见性
  • 有序性(有时需要禁用重排)

内存语义:

  • 当写一个 volatile 变量时, JMM 会把该线程对应的本地内存中的共享变量值立即刷新回主存中
  • 当读一个 volatile 变量时, JMM 会把该线程对应的本地内存设置无效,重新回到主内存中读取最新共享变量
  • 所以 volatile 的写内存语义是直接刷新到主内存中,读的内存语义也是从主内存读取

如何保证可见和有序:

  • 内存屏障

内存屏障

  • 内存屏障(也称内存栅栏,屏障指令等) 是一类同步屏障指令, 是CPU或编译器对内存随机访问的操作的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作,避免代码重排序。内存屏障其实就是一种 JVM 指令, Java 内存模型的重排规则会要求 Java 编译器在生成 JVM 指令时插入特定的内存屏障指令,通过这些内存屏障指令, volatile 实现了 Java 内存模型中的可见性和有序性(禁重排),但 volatile 无法保证原子性。
  • 内存屏障之前的所有写操作都要回写到主内存
  • 内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)
  • 因此重排序后,不允许把内存屏障之后的指令重排序到内存屏障之前。一句话: 对一个 volatile 变量的写,先行发生于任意后续对这个 volatile 变量的读,也叫写后读

写屏障(Store Memory Barrier):

  • 告诉处理器在写屏障之前将所有存储在缓存(store buffers)中的数据同步到主内存。也就是说当看到 Store 屏障指令,就必须把该指令之前所有写入指令执行完毕才能继续往下执行

读屏障(Load Memory Barrier):

  • 处理器在读屏障之后的读操作,都在读屏障之后执行,也就是说在 Load 屏障指令之后就能够保证后面的读取数据指令一定能够读取到最新的数据
  • 在读指令之前插入读屏障,让工作内存或 CPU 高速缓存当中的缓存数据失效,重新回到主内存中获取最新数据

屏障细分四种:

// Unsafe.java
public native void loadFence();
public native void storeFence();
public native void fullFence();

// unsafe.cpp
UNSAFE_ENTRY(void, Unsafe_LoadFence(JNIEnv *env, jobject unsafe))
  UnsafeWrapper("Unsafe_LoadFence");
  OrderAccess::acquire();
UNSAFE_END

UNSAFE_ENTRY(void, Unsafe_StoreFence(JNIEnv *env, jobject unsafe))
  UnsafeWrapper("Unsafe_StoreFence");
  OrderAccess::release();
UNSAFE_END

UNSAFE_ENTRY(void, Unsafe_FullFence(JNIEnv *env, jobject unsafe))
  UnsafeWrapper("Unsafe_FullFence");
  OrderAccess::fence();
UNSAFE_END


// orderAccess.hpp
class OrderAccess : AllStatic {
 public:
  static void     loadload();
  static void     storestore();
  static void     loadstore();
  static void     storeload();

  static void     acquire();
  static void     release();
  static void     fence();

  ...
}


// orderAccess_linux_x86.inline.hpp
inline void OrderAccess::loadload()   { acquire(); }
inline void OrderAccess::storestore() { release(); }
inline void OrderAccess::loadstore()  { acquire(); }
inline void OrderAccess::storeload()  { fence(); }

inline void OrderAccess::acquire() {
  volatile intptr_t local_dummy;
#ifdef AMD64
  __asm__ volatile ("movq 0(%%rsp), %0" : "=r" (local_dummy) : : "memory");
#else
  __asm__ volatile ("movl 0(%%esp),%0" : "=r" (local_dummy) : : "memory");
#endif // AMD64
}

inline void OrderAccess::release() {
  // Avoid hitting the same cache-line from
  // different threads.
  volatile jint local_dummy = 0;
}

inline void OrderAccess::fence() {
  if (os::is_MP()) {
    // always use locked addl since mfence is sometimes expensive
#ifdef AMD64
    __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
    __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
  }
}
屏障类型指令实示例说明
LoadLoadLoad1; LoadLoad; Load2保证 load1 的读取操作在 load2及后续读取操作之前执行
StoreStoreStore1; StoreStore; Store2在 store2 及其后的系操作执行前,保证 store1 的写操作已刷新到主内存
LoadStoreLoad1; LoadStore; Store2在 store2 及其后的写操作执行前,保证 load1 的读操作已经读取结束
StoreLoadStore1;StoreLoad;Load2保证 store1 的写操作已经刷新到主内存之后,load2 及其后的读操作才能执行

volatile 变量规则:

第一个操作第二个操作: 普通读写第二个操作: volatile 读第二个操作: volatile 写
普通读写可以重排可以重排不可以重排
volatile 读不可以重排不可以重排不可以重排
volatile 写可以重排不可以重排不可以重排
  • 当一个操作是 volatile 读时,不论第二个操作是什么,都不能重排序。这个操作保证了 volatile 读之后的操作不会重排到 volatile 读之前
  • 当第二个操作为volatile 写时,不论第一个操作是什么,都不会重排序。这个操作保证了 volatile 写之前的操作不会被重排到 volatile 写之后
  • 当第一个操作为 volatile 写时, 第二个操作为 volatile 读时,不能重排

volatile 读插入内存屏障后生成的指令序列示意图:

  • 在每个 volatile 读操作的后面插入一个 LoadLoad 屏障, 禁止处理器把上面的 volatile 读与下面的普通读重排序。
  • 在每个 volatile 读操作后面插入一个 LoadStore 屏障,  禁止处理器把上面的 volatile 读与下面的普通写重排序

volatile 写插入内存屏障后生成的指令序列示意图:

  • 在每个 volatile 写操作的前面插入一个 StoreStore 屏障,可以保证在 volatile 写之前,其前面的所有普通写操作都已经刷新到主内存中
  • 在每个 volatile 写操作的后面插入一个 StoreLoad 屏障,作用是避免 volatile 写与后面可能有的 volatile 读/写 操作重排序

volatile 特性

保证可见性:

  • 保证不同线程对某个变量完成操作后结果及时可见,即该共享变量一旦改变所有线程立即可见

  • read: 作用于主内存,将变量的值从主内存传输到工作内存
  • load: 作用于工作内存,将 read 从主内存传输的变量值放入工作内存变量副本中,即数据加载
  • use: 作用于工作内存,将工作内存变量副本的值传递给执行引擎,每当 JVM 遇到需要该变量的字节码指令时会执行该操作
  • assign: 作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当 JVM 遇到一个变量赋值字节码会执行该操作
  • store: 作用于工作内存,将赋值完毕的工作变量的值写回给主内存
  • write: 作用于主内存,将 store 传输过来的变量值赋值给主内存的变量

由于 上述6条只能保证单条指令的原子性,针对多条指令的组合原子保证,没有大面积加锁,所以 JVM 提供了另外两个原子指令:

  • lock: 作用于主内存,将一个变量标记为一个线程独占的状态,只是写时候加锁,就只是锁了写变量的过程
  • unlock: 作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用
import java.util.concurrent.TimeUnit;

public class VolatileDemo {
    /**
     * 线程中读取的时候,每次读取都会去主内存中读取共享变量最新的值,然后将其复制到工作内存
     * 线程中修改了工作内存中变量的副本,修改之后会立即刷新到主内存
     */
    static volatile boolean flag = true;
    // 主线程修改了 flag 之后没有将其书信到主内存,所以 t1 线程看不到
    // 主线程将 flag 刷新到了主内存,但是 t1 一直读取的是自己工作内存中的 flag 的值,没有去主内存中更新获取 flag 最新的值
    //static boolean flag = true;
    public static void main(String[] args) {
        new Thread(()-> {
            System.out.println(Thread.currentThread().getName() + "\t ----- come in");

            while(flag) {

            }

            System.out.println(Thread.currentThread().getName() + "\t ----- flag 被设置为 false, 程序停止");
        },"t1").start();

        try{ TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

        flag = false;
        System.out.println(Thread.currentThread().getName() + "\t 修改完成" + flag);

    }
}

原子性:

  • 对于 volatile 变量具有可见性, JVM 只是保证从主内存加载到线程工作内存的值是最新的,也就是数据加载时是最新的,但是多线程环境下,"数据计算" 和 "数据赋值" 操作可能多次出现,若数据在加载之后,若主内存 volatile 修饰变量发生修改之后,线程工作内存中的操作将会被废去读主内存最新值,操作出现写丢失问题。即各线程私有内存和主内存公共内存中变量不同步,进而导致数据不一致。由此可见 volatile 解决的是变量读的可见性问题,但无法保证原子性,对于多线程修改主内存共享变量的场景必须使用加锁同步。
  • 不适合参加依赖当前值的运算,或者能够确保只有单一线程修改变量的值
  • 变量不需要与其他的状态变量共同参与不变约束

import java.util.concurrent.TimeUnit;

class MyNumber{
    int number;

    /*
    *  需要加 synchronized
    * */
    public void addPlusPlus() {
        number ++;
    }
}
public class VolatileNoAtomicDemo {
    public static void main(String[] args) {
        MyNumber myNumber = new MyNumber();

        for (int i = 1; i <= 10; i++) {
            new Thread(()-> {
                for (int j = 1; j <= 1000; j++) {
                    myNumber.addPlusPlus();
                }
            }, String.valueOf(i)).start();
        }

        try{ TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(myNumber.number);
    }
}

正确使用 volatile:

  • 单一赋值可以, but 含复合运算赋值不可以(i++ 之类)
  • 状态标志,判断业务是否结束
  • 开销较低的读,写锁策略
  • DCL 双端锁的发布

volatile 与 操作系统关联:

  •  操作系统通过 ACC_VOLATILE 标识符来按照 JMM 规范增加内存屏障

public class VolatileTest {
    volatile boolean flag;

    public VolatileTest() {
    }

    public static void main(String[] args) {
        System.out.println("------- hello world");
    }
}

  javap -v .\VolatileTest.class

{
  volatile boolean flag;
    descriptor: Z
    flags: ACC_VOLATILE

  public com.bxtech.juc.VolatileTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/bxtech/juc/VolatileTest;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String ------- hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 7: 0
        line 8: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/456377.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

m4a怎么转换成mp3,4招搞定

m4a怎么转换成mp3&#xff1f;相信很多朋友都知道&#xff0c;无论是音频、视频、图片等很多电子文件&#xff0c;都会有多种格式。由于格式不统一&#xff0c;很容易导致文件和软件之间出现不兼容的情况&#xff0c;最直接的结果就是在播放器中无法打开&#xff0c;或者使用的…

一篇文章带你了解Spring/SpringBoot常用注解(建议收藏!!! )

这篇文章介绍的 Spring/SpringBoot 常用注解基本已经涵盖你工作中遇到的大部分常用的场景。对于每一个注解我都说了具体用法&#xff0c;掌握搞懂&#xff0c;使用 SpringBoot 来开发项目基本没啥大问题了&#xff01; 1. SpringBootApplication 这里先单独拎出SpringBootAppl…

NUXT规范及常见问题

props中不要使用Web环境才有的对象&#xff0c;服务端渲染的时候会失败 使用<Nuxt/>组件代替<router-view/>&#xff0c;使用<NuxtLink/>代替<router-link/>static目录下的资源是静态资源&#xff0c;不应该通过import或../static/img/logo.png等方式…

【AI绘画】我以Midjourney为主学习AI绘画效果咋样?

上一篇博客链接&#xff1a;【ChatGPT】ChatGPT掀起AIGC与AI浪潮_山楂山楂丸的博客-CSDN博客 这周&#xff0c;我加入了新星计划&#xff0c; 涉及的领域是我感兴趣以及对未来规划有帮助的——AI绘画&#xff01;​​​​​​​ 文章目录 前言 一、AI绘画是什么 二、AI绘画进…

MYSQL命令小总结

一、创建查看 1.输入cmd&#xff0c;打开控制器&#xff0c;输入如下&#xff0c;打开MYSQL C:\Users\ASUS> mysql -u root -p 2.查看已有数据库 mysql> show databases; 3.建立数据库 4.使用数据库 use englishword;5.建立表单 CREATE TABLE user ( id INT primar…

AI Stable Diffusion Prompt参数【一】

AI Stable Diffusion Prompt参数【一】 配置场景1:草丛里的女性promptNegative Prompt结果 场景2: 雨中披头散发的女孩promptNegative Prompt结果 场景3: 一个女孩和她的朋友在逛街promptNegative Prompt结果 场景4: 学生在教室promptNegative Prompt结果 场景5: 穿着白色丝袜和…

自动化工具 基于 Antd+DRF 开发了一款适配 JMeter 的接口自动化测试报告

JMeter Report 基于 AntdDRF 开发的一款 JMeter 测试报告服务&#xff0c;用于在 JMeter 接口测试中使用。 &#x1f334; 背景 JMeter 是测试工作中常用的一款工具&#xff0c;除了压测还可以用来做接口自动化的测试。 从事测试多年&#xff0c;接口自动化也做过很多的尝试…

从零搭建开发脚手架 自定义打包构建研发输出产物

文章目录 背景目录源码目录结果目录 实现项目打包方式一 使用IDEA工具打包,选择对应的profiles,然后clean package方式二 使用maven命令打包 部署 背景 在打包项目时&#xff0c;通常只需要生成一个可执行的jar文件。然而&#xff0c;有时候需要将启动脚本或外部配置文件附加到…

Java版电子招投标系统源码之了解电子招标投标全流程

随着各级政府部门的大力推进&#xff0c;以及国内互联网的建设&#xff0c;电子招投标已经逐渐成为国内主流的招标投标方式&#xff0c;但是依然有很多人对电子招投标的流程不够了解&#xff0c;在具体操作上存在困难。虽然各个交易平台的招标投标在线操作会略有不同&#xff0…

感应开关盖垃圾桶项目实现.md

1.项目接线 接线示意图和实物图 示意图和接线说明&#xff1a; 舵机控制口P1.1&#xff08;定时器0中断&#xff09;&#xff1b;超声波Trig接P1.5 ,Echo接P1.4 &#xff1b;蜂鸣器接P2.0 口;震动传感器接P3.2 口&#xff08;外部中断0&#xff09;。 实物图&#xff08;未封装…

室内人员定位,硬件部署方案多场景技术分析

室内外定位技术在工业、矿业、医疗养老、能源电力、展览展会、公检法司、工地等众多领域具有丰富的应用场景。定位能力作为“物联网”的核心能力之一&#xff0c;其定位精度和稳定性不仅需要强大的算法支撑&#xff0c;也有赖于好的室内定位设备的落地部署。 基于蓝牙LoRa定位…

MySQL按照,库的操作,表的约束 --- MySQL总结(一)

MySQL 文章目录 MySQLMySQL的安装MySQL组成架构连接池软件层引擎层储存层 库的操作创建库修改数据库删除数据库备份数据库还原数据库查看链接情况 表操作创建表查看表结构修改表结构添加字段修改字段类型长度更改表名修改属性删除表 数据类型表的约束空属性&#xff08;not nul…

typescript全局安装卸载以及npm相关问题

全局安装 npm install -g typescript 全局安装之后&#xff0c;如果想要卸载要使用 npm uninstall -g typescript 全局安装之后可以在终端使用 tsc xxx 编译ts文件 本地安装&#xff0c;也就是在项目目录下安装 npm install typescript 本地卸载 npm uninstall type…

【LeetCode】刷题数据结构(1)[反转链表]

【LeetCode】刷题数据结构&#xff08;1&#xff09; 1.题目来源2.题目描述3.解题思路4.代码展示 所属专栏&#xff1a;玩转数据结构题型 博主首页&#xff1a;初阳785 代码托管&#xff1a;chuyang785 感谢大家的支持&#xff0c;您的点赞和关注是对我最大的支持&#xff01;&…

Elasticsearch ILM DSL 索引生命周期管理

1、冷热集群架构 冷热架构也叫冷暖架构&#xff0c;是“Hot-Warm” Architecture的中文翻译。 冷热架构本质是给节点设置不同的属性&#xff0c;让每个节点具备了不同的属性。为演示 ILM&#xff0c;需要首先配置冷热架构&#xff0c;三个节点在 elasticsearch.yml 分别设置的…

4月21号软件更新资讯合集.....

PlayEdu v1.0-beta.3 发布&#xff0c;视频培训解决方案 PlayEdu 是基于 SpringBoot3 Java17 React18 开发的企业内部培训系统。它专注于提供私有化部署方案&#xff0c;包括视频&#xff0c;图片等资源的内网部署。目前主要支持有本地视频上传播放、学员邮箱登录、无限级部门…

如何使用JMeter和Ant生成高效测试报告?

Jmeter接口自动化测试项目实战视频教程地址&#xff1a;https://www.bilibili.com/video/BV1e44y1X78S/? 目录&#xff1a;导读 引言 一、安装ant 二、ant关联jmeter工具 三、执行 结语 引言 你曾经在进行软件测试时遇到过测试结果难以分析&#xff0c;甚至花费大量时间…

机器学习基础知识之相关性分析

文章目录 相关性分析定义1、图表相关性分析2、协方差分析3、相关系数分析4、回归分析 相关性分析定义 相关性分析一般是指通过对两种或两种以上的变量数据进行数学分析来确定两种或两种以上的变量数据之间的相关密切程度。由此定义我们可以得知相关性分析的目的在于衡量变量数…

算法leetcode|48. 旋转图像(rust重拳出击)

文章目录 48. 旋转图像&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 48. 旋转图像&#xff1a; 给定一个 n n 的二维矩…

我国风电叶片行业分析:行业技术创新白热化竞争来临 后续回收再利用是行业可持续发展的关键

1、风电叶片行业定义及产业链 风电叶片是一个复合材料制成的薄壳结构&#xff0c;一般由外壳、腹板和主梁三部分组成&#xff0c;复合材料在整个风电叶片中的重量一般占到90%以上。风电叶片是风力发电机将风能转化为机械能的重要部件之一&#xff0c;其设计、制造及运行状态直…