JUC并发编程第七篇,volatile凭什么可以保证可见性和有序性?我们该如何正确使用它?

news2024/10/3 8:22:50

JUC并发编程第七篇,volatile凭什么可以保证可见性和有序性?我们该如何正确使用它?

    • 一、volatile的作用是什么?
    • 二、什么是内存屏障?
    • 三、四大内存屏障指令源码解析!
    • 四、volatile如何通过内存屏障保证可见性和有序性?
      • 1. 保证可见性
      • 2. 指令禁重排
      • 3. 底层具体实现原理?
    • 五、volatile为什么不能保证原子性?
    • 六、如何正确的使用volatile?

一、volatile的作用是什么?

  • 我们都知道,被 volatile 修饰的变量具有两大特点:可见性、有序性

这里说的 可见性 和 有序性 的含义是什么呢?

  • 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。
  • 当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量。

也就是说:volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取。

二、什么是内存屏障?

  • 内存屏障是一种同步屏障指令,是CPU或编译器在对内存随机访问操作中的一个同步点,目的是为了保证此点之前的所有读写操作都执行完,然后才可以开始执行此点之后的操作,避免代码重排序。

  • 内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的 可见性和有序性,但volatile无法保证原子性。
    在这里插入图片描述

  • 内存屏障之前的所有写操作都要回写到主内存,内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果。

  • 简单说就是:对一个 volatile 域的写, happens-before 于任意后续对这个 volatile 域的读。

三、四大内存屏障指令源码解析!

  • 在 rt.jar 中有一个 Unsafe 类,如下:
    在这里插入图片描述
  • 它里边有三个 native 方法,继续追寻对应的C++源码如下:
    在这里插入图片描述
    在这里插入图片描述
  • 四大屏障分别是什么意思呢?
    在这里插入图片描述

四、volatile如何通过内存屏障保证可见性和有序性?

1. 保证可见性

  • 保证不同线程对这个变量进行操作时的可见性,即变量一旦改变所有线程立即可见
public class VolatileSeeDemo
{
    static          boolean flag = true;       //不加volatile,没有可见性
    //static volatile boolean flag = true;       //加了volatile,保证可见性

    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();

        //暂停2秒钟后让main线程修改flag值
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

        flag = false;

        System.out.println("main线程修改完成");
    }
}
  • 如果不加volatile,程序就无法停止,因为:

1.主线程修改 flag 之后没有立即刷新回主内存
2.主线程将flag刷新到了主内存,但是 t1 线程没有去获取更新。

2. 指令禁重排

  • volatile的底层通过 内存屏障 禁止指令重排
    在这里插入图片描述
  • 当第一个操作为volatile读时,不论第二个操作是什么,都不能重排序。
  • 当第二个操作为volatile写时,不论第一个操作是什么,都不能重排序。
  • 当第一个操作为volatile写时,第二个操作为volatile读时,不能重排。

3. 底层具体实现原理?


  • 在这里插入图片描述

在每个 volatile 写操作的前⾯插⼊⼀个 StoreStore 屏障

在每个 volatile 写操作的后⾯插⼊⼀个 StoreLoad 屏障


  • 在这里插入图片描述

在每个 volatile 读操作的后⾯插⼊⼀个 LoadLoad 屏障

在每个 volatile 读操作的后⾯插⼊⼀个 LoadStore 屏障

五、volatile为什么不能保证原子性?

  • volatile变量的复合操作不具有原子性,比如 i++
  • 原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。
  • i++ 为复合操作,它分为:读取值,然后加一,最后写回三步,在这期间如果有另一个线程过来读取,就会造成线程安全问题。

volatile 不是保证可见嘛,怎么还会出现上边的问题?

  • volatile 变量读写过程:

read(读取)→load(加载)→use(使用)→assign(赋值)→store(存储)→write(写入)→lock(锁定)→unlock(解锁)
在这里插入图片描述

  • 如上图:
  • 要use(使用)一个变量的时候必需 load(载入),要载入的时候必需从主内存 read(读取)解决读的可见性。
  • 写操作是把 assign 和 store 做了关联,在 assign(赋值) 后必需 store(存储),store(存储) 后 write(写入)。
  • 通过用的时候直接从主内存取,在赋值到直接写回主内存做到了内存可见性。

但是注意 蓝色的间隙,read-load-use 和 assign-store-write 是两个不可分割的原子操作,但是在 use 和 assign 之间依然有极小的一段真空期,有可能变量会被其他线程读取,丢失一次写数据。
在这里插入图片描述
因此volatile变量不适合参与到依赖当前值的运算,但是无论在哪一个时间点主内存的变量和任一工作内存的变量的值都是相等的,所以通常volatile用做保存某个状态的 boolean 值或 int 值。

六、如何正确的使用volatile?

  • 1. 单一赋值时可以使用,复合运算赋值不可以使用

volatile int a = 10;

  • 2. 一次性的状态标志时使用

volatile boolean flag = true;

  • 3. 开销较低的读、写策略时使用
public class UseVolatileDemo{
    /**
     * 使用:当读远多于写,结合使用内部锁和 volatile 变量来减少同步的开销
     * 理由:利用volatile保证读取操作的可见性;利用synchronized保证复合操作的原子性
     */
    public class Counter{
        private volatile int value;

        public int getValue(){
            return value;   //利用volatile保证读取操作的可见性
        }
        public synchronized int increment(){
            return value++; //利用synchronized保证复合操作的原子性
        }
    }
}
  • 4. DCL双端锁的发布
public class SafeDoubleCheckSingleton
{
    //通过volatile声明,实现线程安全的延迟初始化。
    private volatile static SafeDoubleCheckSingleton singleton;
    //私有化构造方法
    private SafeDoubleCheckSingleton(){
    }
    //双重锁设计
    public static SafeDoubleCheckSingleton getInstance(){
        if (singleton == null){
            //1.多线程并发创建对象时,会通过加锁保证只有一个线程能创建对象
            synchronized (SafeDoubleCheckSingleton.class){
                if (singleton == null){
                    singleton = new SafeDoubleCheckSingleton();
                }
            }
        }
        //2.对象创建完毕,执行getInstance()将不需要获取锁,直接返回创建对象
        return singleton;
    }
}

上边为双重检查锁的单例模式的写法,使用volatile禁重排,防止获取对象为空
如果不想使用 volatile,也可以使用静态内部类写法,与上边作用相同

public class SingletonDemo
{
    private SingletonDemo() { }

    private static class SingletonDemoHandler{
        private static SingletonDemo instance = new SingletonDemo();
    }

    public static SingletonDemo getInstance(){
        return SingletonDemoHandler.instance;
    }
}

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

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

相关文章

Spring MVC数据绑定和表单标签的应用(附带实例)

为了让读者进一步学习数据绑定和表单标签,本节给出了一个应用范例 springMVCDemo04。该应用中实现了 User 类属性和 JSP 页面中表单参数的绑定,同时在 JSP 页面中分别展示了 input、textarea、checkbox、checkboxs、select 等标签。 应用的相关配置 在…

解析csv文件,读取百万级数据

最近在处理下载支付宝账单的需求,支付宝都有代码示例,功能完成还是比较简单的,唯一的问题就在于下载后的文件数据读取。账单文件可大可小,要保证其可用以及性能就不能简单粗暴的完成开发就行。 文件下载是是csv格式,此…

干货|成为优秀软件测试工程师的六大必备能力

“软件吞噬世界”、“软件定义一切”。随着软件行业的迅速发展,保障软件质量的关键环节——软件测试也变得越来越重要。而执行测试工作的测试工程师,便是软件质量的把关者。 测试工程师早在2005年就被劳动和社会保障部门列入第四批新职业中。经过短短几…

文件批量从gbk转成utf8的工具

工具名:GB/BIG5/UTF-8 文件编码批量转换程序 下载地址: https://www.wenjiangs.com/wp-content/uploads/2018/05/GB2UTF8.zip 程序功能:将 GB、BIG5、UTF-8 文件相互转换,方便的批量处理能力,主要用于网站文件编码方式…

单商户商城系统功能拆解41—应用中心—用户储值

单商户商城系统,也称为B2C自营电商模式单店商城系统。可以快速帮助个人、机构和企业搭建自己的私域交易线上商城。 单商户商城系统完美契合私域流量变现闭环交易使用。通常拥有丰富的营销玩法,例如拼团,秒杀,砍价,包邮…

R语言进行相关矩阵分析及其网络可视化

数据准备 # 选择感兴趣的列 mydata <- mtcars %>% select(mpg, disp, hp, drat, wt, qsec) # 添加一些缺失值 mydata$hp[3] <- NA # 检查数据 head(mydata, 3) ## mpg disp hp drat wt qsec ## Mazda RX4 21.0 160 110 3.90 2.62 16.5 ## Ma…

基于WiFi小车控制板的单片机小系统原理图

小系统&#xff0c;指的是的用少的元件组成的单片机可以工作的系统。一般在设计小系统的时候分为这四种必要的电路&#xff0c;分别是1、电源供电电路;2、单片机复位电路;3、时钟振荡电路;4、程序的下载接口电路。这次基于我们研发的WiFi小车51核心控制板的小系统原理图来讲一下…

uni-app实现支付及项目打包上传

本文主要介绍uni-app项目中如何实现支付功能&#xff08;支付宝支付、微信支付&#xff09;&#xff0c;及项目如何打包上传。 一、实现支付 前置工作&#xff0c;项目要实现支付功能&#xff0c;首先要在根目录manifest.json文件内App模块配置中进行设置。 其中&#xff0c;a…

机构运动学分析

背景介绍 空间机构具有结构紧凑、运动灵活等特点&#xff0c;在航空航天、精密仪器以及工业设备等领域具有广泛的应用。调研发现&#xff0c;机械臂一般采用伺服电机作为动力源&#xff0c;通过空间连杆驱动末端执行器&#xff0c;大大的减轻了工人的劳动强度。本节中主要是针对…

iconfont小图标从下载到引入到vue项目中的详细教程

地址&#xff1a;iconfont-阿里巴巴矢量图标库 iconfont小图标下载&#xff1a; &#xff08;1&#xff09;查找图标 在搜索框直接文字搜索或者看下面的小图标库&#xff0c;找想要的&#xff0c;每个小图标库都有一个名字&#xff0c;比如&#xff1a;“阿里云官网”&#x…

Vue2中$set的使用

一、什么场景下使用$set set为解决Vue2中双向数据绑定失效而生&#xff0c;只需要关注什么时候双向数据绑定会失效就可以了。 例如&#xff1a; 1.利用数组中某个项的索引直接修改该项的时候 arr[indexOfItem] newValue 2.直接修改数组的长度的时候 arr.length newLength …

gRPC学习笔记(一)

文章目录gRPC初学思维导图异步多函数多类的调用grpc初学总结&#xff1a;杂项gRPC初学思维导图 异步多函数多类的调用 一个类里有多个方法时&#xff0c; 两种方法&#xff1a; 定义不同的类&#xff08;推荐&#xff0c;只管自己的实现&#xff0c;换了请求类型&#xff0c;…

在linux系统上看全世界新闻 -- Clinews的使用详解

一. Clinews介绍 Clinews 和 InstantNews 类似&#xff0c;都是 Linux 命令行下的新闻客户端&#xff0c;安装及配置 Clinews 后就可以在 Linux 命令行下阅读新闻及头条新闻了&#xff0c; 当然还有博客新闻&#xff0c;不需要安装 GUI 应用或移动应用&#xff0c;轻松在 Linu…

值得收藏的30道Python练手题(附详解)

今天给大家分享30道Python练习题&#xff0c;建议大家先独立思考一下解题思路&#xff0c;再查看答案。 1.已知一个字符串为 “hello_world_yoyo”&#xff0c;如何得到一个队列 [“hello”,”world”,”yoyo”] &#xff1f; 使用 split 函数&#xff0c;分割字符串&#xf…

2022年最热门的短网址整理,让你不再选择恐惧

转眼一年又过去了&#xff0c;最近发现网络上有各种各样的短网址平台&#xff0c;让人眼花缭乱&#xff0c;都声称免费并且功能强大&#xff0c;但是据我的了解&#xff0c;很多免费的短网址都是有使用上的限制的&#xff0c;比如生成条数、访问次数、有广告等等、还有各种各样…

校招|拿到腾讯、阿里、字节等10家互联网测试开发岗的offer

前言 首先自我介绍一下&#xff0c;本人北京地区985本硕&#xff0c;工科非计算机专业&#xff0c;课程、毕设课题和编程以及测开都一点关系也没有。但是&#xff0c;通过自己的准备和实习积累的经验&#xff0c;在秋招的时候收获了10家互联网公司的测试开发岗和北京地区一些国…

数字图像处理(入门篇)四 像素关系

目录 1 像素关系 2 像素的领域 &#xff08;1&#xff09;4-邻域 &#xff08;2&#xff09;对角邻域 &#xff08;3&#xff09;8-领域 3 像素的邻接和连接 &#xff08;1&#xff09;4-连接 &#xff08;2&#xff09;8-连接 4 像素的连通 5 连通域 6 像素之间的距…

HMM隐马尔可夫模型

1.概率图模型&#xff1a;HMM&#xff08;隐马&#xff09;,MEMM&#xff08;最大熵&#xff09;,CRF&#xff08;条件随机场&#xff09;概率&#xff1a;既然是一个图那么就是一个有圈有边的结构&#xff0c;圈代表随机向量&#xff0c;随机变量之间有边&#xff0c;边上有概…

互联网企业面试必问 Spring 源码? 拿下Spring 源码,看完这篇就够了

前言 不用说&#xff0c;Spring 已经成为 Java 后端开发的事实上的行业标准。无数公司选择 Spring 作为基本开发框架。大多数 Java 后端程序员在日常工作中也会接触到 Spring。因此&#xff0c;如何很好地使用 Spring&#xff0c;已成为 Java 程序员的必修课之一。 同时&…