返回顶部
首页 > 资讯 > 后端开发 > JAVA >Java中的volatile
  • 640
分享到

Java中的volatile

java多线程volatile 2023-09-03 20:09:21 640人浏览 泡泡鱼
摘要

文章目录 1、volatile的内存语义2、内存屏障2、happens-before 之 volatile 变量规则4、Demo 1、volatile的内存语义 内存可见性 ​

文章目录

1、volatile的内存语义

内存可见性

​ volatile是Java提供的一种轻量级的同步机制,在并发编程中,它也扮演着比较重要的角色。同synchronized相比(synchronized通常称为重量级),volatile更轻量级,相比使用synchronized所带来的庞大开销,倘若能恰当的合理的使用volatile,自然是美事一桩。

为了能比较清晰彻底的理解volatile,我们一步一步来分析。首先来看看如下代码

public class volatileDemo1 {    static boolean flag = true;    public static void main(String[] args) {        new Thread(()->{            System.out.println(Thread.currentThread().getName()+"\t -----come in");            try {                TimeUnit.SECONDS.sleep(2);            } catch (InterruptedException e) {                e.printStackTrace();            }            flag = false;        },"t1").start();        new Thread(()->{            System.out.println(Thread.currentThread().getName()+"\t -----come in");            while (flag) {            }            System.out.println(Thread.currentThread().getName()+"\t -----flag被设置为false,程序停止");        },"t2").start();    }}

上面这个例子,模拟在多线程环境里,t1线程对flag共享变量修改的值能否被t2可见,即是否输出 “-----flag被设置为false,程序停止” 这句话?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vzNZ5vt5-1656404046754)(JUC并发编程.assets/image-20220628160812458.png)]

答案是:NO! 输出结果如下~
在这里插入图片描述

​ 这个结论会让人有些疑惑,可以理解。因为倘若在单线程模型里,因为先行发生原则之happens-before,自然是可以正确保证输出的;但是在多线程模型中,是没法做这种保证的。因为对于共享变量flag来说,线程t1的修改,对于线程t2来讲,是"不可见"的。也就是说,线程t2此时可能无法观测到flage已被修改为false。那么什么是可见性呢?

所谓可见性,是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道该变更,JMM规定了所有的变量都存储在主内存。很显然,上述的例子中是没有办法做到内存可见性的。

volatile的内存语义

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

所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取,从而保证了可见性。

volatile变量有2大特点,分别是:

  • 可见性

  • 有序性:禁重排!

    排序是指编译器和处理器为了优化程序性能面对指令序列进行重新排序的一种手段,有时候会改变程序予以的先后顺序。

    • 不存在数据以来关系,可以重排序;
    • 存在数据依赖关系,禁止重排序。

    但重排后的指令绝对不能改变原有串行语义!

那么volatile凭什么可以保证可见性和有序性呢??

  • 内存屏障Memory Barrier~

2、内存屏障

​ 内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。

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

  • 内存屏障之前 的所有 操作 都要 回写到主内存,
  • 内存屏障之后 的所有 操作 都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)。

粗分主要是以下两种屏障:

  • 读屏障(Load Memory Barrier) :在读指令之前插入读屏障,让工作内存或CPU高速缓存当中的缓存数据失效,重新回到主内存中获取最新数据。
  • 写屏障(Store Memory Barrier) :在写指令之后插入写屏障,强制把写缓冲区的数据刷回到主内存中。

因此重排序时,不允许把内存屏障之后的指令重排序到内存屏障之前。一句话:对一个volatile变量的写,先行发生于任意后续对这个volatile变量的读,也叫写后读。

让我们来看看源码:sun.misc.Unsafe.java
在这里插入图片描述
主要包括以上三个方法,接着在对应的Unsafe.cpp 源码中查看:
在这里插入图片描述
在底层c++代码中发现其底层调用的是OrderAccess类中的方法~
在这里插入图片描述
我们发现其又细分了四种屏障,四大屏障分别是什么意思呢?

屏障类型指令示例说明
LoadLoadLoad1;LoadLoad;Load2保证Load的读取操作在load2及后续读取操作之前执行
StoreStoreStore1;StoreStore;Store2在Store2及其后的写操作执行前,保证store1的写操作已刷新到主内存
LoadStoreLoad1;LoadLoad;Store2在Store2及其后的写操作执行前,保证Load1的读操作已读取结束
StoreLoadStore1;StoreStore;Load2保证Store1的写操作已刷新到主内存之后,Load2及其后的读操作才能执行

接下来结合底层linux_86代码来分析~
在这里插入图片描述

2、happens-before 之 volatile 变量规则

在这里插入图片描述

给大家讲解一下上表,主要有以下三种情况不允许重拍~

  1. 蓝色:当第一个操作为volatile读时,不论第二个操作是什么,都不能重排序。这个操作保证了volatile读之后的操作不会被重排到volatile读之前。

  2. 红色:当第二个操作为volatile写时,不论第一个操作是什么,都不能重排序。这个操作保证了volatile写之前的操作不会重排序到volatile写之后。

  3. 绿色:当第一个操作为volatile写时,第二个操作为volatile读时,不能重排。

其他情况都允许被重排。

4、Demo

可见性案例

public class volatileDemo1 {    static volatile boolean flag = true;    public static void main(String[] args) {        new Thread(()->{            System.out.println(Thread.currentThread().getName()+"\t -----come in");            try {                TimeUnit.SECONDS.sleep(2);            } catch (InterruptedException e) {                e.printStackTrace();            }            flag = false;        },"t1").start();        new Thread(()->{            System.out.println(Thread.currentThread().getName()+"\t -----come in");            while (flag) {            }            System.out.println(Thread.currentThread().getName()+"\t -----flag被设置为false,程序停止");        },"t2").start();    }}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DVv5C5mZ-1656423701210)(JUC并发编程.assets/image-20220628161643842.png)]

若不加volatile修饰为何t2 看不到被 t1线程修改为 false的flag的值?

  1. t1线程修改了flag之后没有将其刷新回住内存,所以t2线程获取不到。
  2. 主线程将flag刷新到了主内存,但是t2一直读取的是自己工作内存中flag的值,没有去主内存中更新获取flag最新的值。

使用volatile修饰共享变量后,被volatile修饰的变量有以下特点:

  1. 线程中读取时候,每次读取都会去主内存中读取共享变量最新的值,然后将其复制到工作内存;
  2. 线程中修改了工作内存中变量的副本,修改之后回立即刷新到主内存。

无原子性案例

首先我们先编写一个用 synchronized 修饰的案例:

class MyNumber {    int number;    public synchronized void addPlusPlus() {        number++;    }}

在我们的main方法中开启一个线程执行 number++的方法,然后等待2秒,大家的预期值是不是1000呢?

public class volatileDemo2 {    public static void main(String[] args) {        MyNumber myNumber = new MyNumber();        for (int i = 0; i < 10; i++) {            new Thread(()->{                for (int i1 = 0; i1 < 1000; i1++) {                    myNumber.addPlusPlus();                }            }).start();        }        try {            TimeUnit.SECONDS.sleep(1);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(myNumber.number);    }}

结果是一致的,因为使用了 synchronized 修饰了number++方法,从而保证了原子性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RhetVkfU-1656423701211)(JUC并发编程.assets/image-20220628163421068.png)]

接下来 使用 volatile修饰number~

class MyNumber {    volatile int number;    public void addPlusPlus() {        number++;    }}

在这里插入图片描述

那为什么会出现不预期的结果呢?

​ 对于volatile变量具备可见性,JVM只是保证从主内存加载到线程工作内存的值是最新的,也仅是数据加载时是最新的。但是在多线程环境下,“数据计算” 和 “数据赋值” 操作可能多次出现,若数据在加载之后,若主内存volatile修饰变量发生修改之后,线程工作内存中的操作将会作废去读主内存的最新值,操作出现丢失问题。即 各线程工作内存和主内存中变量不同步,进而导致数据不一致。由此可见volatile解决的是变量读时的可见性问题,但无法保证原子性,对于多线程修改主内存共享变量的场景必须使用加锁同步。

禁重排

public class volatileDemo3 {    int i = 0;    volatile boolean flag = false;    public void write() {        if (flag) {            System.out.println("---i=" + i);        }    }}

在本案例中 变量i 和 flag 语句的执行顺序如果被重排的话就会影响结果,存在数据依赖关系,禁止重排序。

说了这么多,那么在什么时候使用 volatile 呢?

  1. 单一赋值可以,但含复合运算赋值不可以
  2. 状态标志,判断业务是否结束
  3. 开销较低的读,写锁策略
  4. DCL双端锁的发布

这里要提提 DCL双端锁,小编最近面试有被问到~在接下来的博客里给大家谈谈单例模式

来源地址:https://blog.csdn.net/m0_49183244/article/details/125493673

--结束END--

本文标题: Java中的volatile

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

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

猜你喜欢
  • Java中的volatile
    文章目录 1、volatile的内存语义2、内存屏障2、happens-before 之 volatile 变量规则4、Demo 1、volatile的内存语义 内存可见性 ​ ...
    99+
    2023-09-03
    java 多线程 volatile
  • Java中volatile 的作用
    目录内存可见性禁止指令重排序总结前言: volatile 是 Java 并发编程的重要组成部分,也是常见的面试题之一,它的主要作用有两个:保证内存的可见性和禁止指令重排序。下面我们具...
    99+
    2024-04-02
  • java中的volatile关键字
    目录1.volatile实现可见性的原理是什么?2.演示volatile的可见性1.volatile实现可见性的原理是什么? 有volatile变量修饰的共享变量进行写操作的时候汇编...
    99+
    2024-04-02
  • java中的volatile怎么应用
    这篇文章主要介绍了java中的volatile怎么应用的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇java中的volatile怎么应用文章都会有所收获,下面我们一起来看看吧。在某些情况下,volatile关键...
    99+
    2023-06-30
  • Java中volatile关键字的作用
    目录一、volatile作用二、什么是可见性三、什么是总线锁和缓存锁四、什么是指令重排序一、volatile作用 可以保证多线程环境下共享变量的可见性通过增加内存屏障防止多个指令之间...
    99+
    2024-04-02
  • Java中怎么使用volatile
    这篇文章主要介绍了Java中怎么使用volatile的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java中怎么使用volatile文章都会有所收获,下面我们一起来看看吧。 ...
    99+
    2024-04-02
  • 怎么在java中使用volatile
    本篇文章为大家展示了怎么在java中使用volatile,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Java是什么Java是一门面向对象编程语言,可以编写桌面应用程序、Web应用程序、分布式系统和...
    99+
    2023-06-14
  • Java中JMM与volatile关键字的学习
    目录JMMvolatile关键字可见性与原子性测试哪些地方用到过volatile?单例模式的安全问题你知道CAS吗?CAS底层原理CAS缺点ABA问题总结JMM JMM是指Java内...
    99+
    2024-04-02
  • java中volatile变量的原理是什么
    这篇文章给大家介绍java中volatile变量的原理是什么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3. 客户端开发;4. 网...
    99+
    2023-06-14
  • java中的volatile关键字怎么使用
    本篇内容介绍了“java中的volatile关键字怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1.volatile实现可见性的原理...
    99+
    2023-06-25
  • Java中的volatile关键字有什么用
    本篇内容主要讲解“Java中的volatile关键字有什么用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java中的volatile关键字有什么用”吧!一、volatile作用可以保证多线程环...
    99+
    2023-06-30
  • java中volatile和synchronized的区别与联系
    java中volatile和synchronized的区别与联系这个可能是最好的对比volatile和synchronized作用的文章了。volatile是一个变量修饰符,而synchronized是一个方法或块的修饰符。所以我们使用这两...
    99+
    2023-05-31
    volatile synchronized ava
  • Java中static和volatile关键字的区别
    1. 作用范围不同 static关键字:用于创建类级别的变量或方法,所有类的实例共享同一个static变量的副本。 volatile关键字:用于确保一个变量在多线程环境中的可见性,使所有线程都能看到最新的变量值。 2....
    99+
    2023-10-29
    关键字 区别 Java
  • Java中volatile防止指令重排
    目录什么是指令重排? 为什么指令重排能够提高性能 volatile是怎么禁止指令重排的? volatile可以防止指令重排,在多线程环境下有时候我们需要使用volat...
    99+
    2024-04-02
  • Java中Volatile变量有什么用
    这篇文章将为大家详细讲解有关Java中Volatile变量有什么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Volatile关键字是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步...
    99+
    2023-06-25
  • 深度理解Java中volatile的内存语义
    volatile可见性实验 举个栗子 我这里开了两个线程,后面的线程去修改volatile变量,前面的线程不断获取volatile变量, 结果是会一致卡在死循环,控制台没有任何输出...
    99+
    2024-04-02
  • 在java中synchronized和volatile的区别是什么
    java中synchronized和volatile的区别:volatile仅能使用在变量级别,而synchronized则可以使用在变量、方法、和类级别的。synchronized会线程阻塞,volatile不会造成线程阻塞。synchr...
    99+
    2024-04-02
  • 多方面解读Java中的volatile关键字
    目录介绍作用保证变量的可见性:禁止指令重排:不能保证原子性可见性、有序性、原子性不会导致线程阻塞使用场景实现原理happens-before局限性和 synchronized 关键字...
    99+
    2023-05-19
    Java volatile volatile作用 volatile关键字
  • 深入浅析java 中volatile与lock的原理
    深入浅析java 中volatile与lock的原理?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。java 中volatile和lock原理分析volatile和lock是...
    99+
    2023-05-31
    java volatile lock
  • java volatile案例讲解
    本篇来自java并发编程实战关于volatile的总结。 要说volatile,先得明白内存可见性。那我们就从内存可见性说起。 一、内存可见性 可见性是一种复杂的属性,因为可见性中的...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作