0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

volatile的原理

科技绿洲 来源:Java技术指北 作者:Java技术指北 2023-10-10 16:33 次阅读

今天来了解一下面试题:你对 volatile 了解多少。要了解 volatile 关键字,就得从 Java 内存模型开始。最后到 volatile 的原理。

一、Java 内存模型 (JMM)

大家都知道 Java 程序可以做到一次编写然后到处运行。这个功劳要归功于 Java 虚拟机。Java 虚拟机中定义了一种 Jva 内存模型(JMM),用来屏蔽掉各种硬件操作系统之间内存访问差异,让 Java 程序可以在各个平台中访问变量达到相同的效果。

JMM 的主要目标是定义了程序中变量的访问规则,就是内存中存放和读取变量的一些底层的细节。

JMM 规则

  1. 变量包含实例字段,静态字段,构成数组对象的元素,不包含局部变量和方法参数
  2. 变量都存储在主内存上。
  3. 每个线程在 CPU 中都有自己的 工作内存 ,工作内存保存了被该线程使用到的变量的主内存副本拷贝。
  4. 线程对变量的所有操作都只能在工作内存,不能直接读写主内存的变量。
  5. 不同线程之间无法之间访问对方工作内存中的变量。

图片

定义一个静态变量: static int a = 1;

线程 1 工作内存指向主内存操作
----a = 1--
a = 1<--a = 1线程 1 拷贝主内存变量副本
a = 3--a = 1线程 1 修改工作内存变量值
a = 3-->a = 3线程 1 工作内存变量存储到主内存变量

上面的一系列内存操作,在 JMM 中定义了 8 种操作来完成。

JMM 交互

主内存和工作内存之间的交互,JMM 定义了 8 种操作来完成,每个操作都是原子性的。

  1. lock (锁定): 作用于主内存变量,把一个变量标识为一条内存独占的状态。
  2. unlock (解锁): 作用于主内存变量,把 lock 状态的变量释放出来,释放出来后才能被其他线程锁定。
  3. read (读取): 作用于主内存变量,把一个变量的值从主内存传输到工作内存中。
  4. load (载入): 作用于工作内存变量,把 read 操作的变量放入到工作内存副本中。
  5. use (使用): 作用于工作内存变量,把工作内存中的变量的值传递给执行引擎,每当虚拟机遇到需要这个变量的值的字节码指令时都执行这个操作。
  6. assgin (赋值): 作用于工作内存变量,把从执行引擎收到的值赋值给工作内存变量,每当虚拟机遇到需要赋值变量的值的字节码指令时都执行这个操作。
  7. store (存储): 作用于工作内存变量,把工作内存中的一个变量值,传送到主内存。
  8. write (写入): 作用于主内存变量,把 store 操作的从工作内存取到的变量写入主内存变量中。

图片

从上图中可知,JMM 交互在一条线程中是不会出现任何的问题。但是当有两条线程的时候,线程 1 已经修改了变量的值,但是并未刷新到主内存时,如果此时线程 2 读取变量得到的值并不是线程 1 修改过的数据。

当引入线程 2 的时候 定义一个静态变量: static int a = 1;

操作顺序线程 1 工作内存线程 2 工作内存指向主内存操作
--------a = 1--
1a = 1--<--a = 1线程 1 拷贝主内存变量副本
2a = 3----a = 1线程 1 修改工作内存变量值
3a = 3---->a = 1线程 1 工作内存变量存储到主内存变量,主内存变量还未更新
4.1a = 3a = 1<--a = 3线程 2 拷贝主内存变量副本随后主内存变量更新线程 1 工作内存变量
4.2a = 3a = 1<--a = 3线程 1 工作内存变量存储到主内存变量随后线程 2 获取主内存变量副本

下面就可以用 volatile 关键字解决问题。

二、volatile

volatile 可以保证变量对所有线程可见,一条线程修改的值,其他线程对新值可以立即得知。还可以禁止指令的重排序。

可见性

修改内存变量后立刻同步到主内存中,其他的线程立刻得知得益于 Java 的先行发生原则

先行发生原则中的 volatile 原则:一个 volatile 变量的写操作先行于后面发生的这个变量的读操作

定义一个静态变量: static int a = 1;

线程 1 工作内存线程 2 工作内存指向主内存操作
------a = 1--
a = 1--<--a = 1线程 1 拷贝主内存变量副本
a = 3----a = 1线程 1 修改工作内存变量值
a = 3---->a = 1线程 1 工作内存变量存储到主内存变量
a = 3a = 3<--a = 3volatile 原则: 主内存变量保存线程A工作内存变量操作在线程 2 工作内存读取主内存变量操作之前

可见性原理

对 volatile 修饰的变量,在执行写操作的时候会多出一条 lock 前缀的指令。JVM 将 lock 前缀指令发送给 CPU ,CPU 处理写操作后将最后的值立刻写回主内存,因为有 MESI 缓存一致性协议保证了各个 CPU 的缓存是一致的,所以各个 CPU 缓存都会对总线进行嗅探,本地缓存中的数据是否被别的线程修改了。

如果别的线程修改了共享变量的数据,那么 CPU 就会将本地缓存的变量数据过期掉,然后这个 CPU 上执行的线程在读取共享变量的时候,就会从主内存重新加载最新的数据。

原子性

volatile 并不保证变量具有原子性。

public class VolatileTest implements Runnable {

    public static volatile int num;

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            num++;
        }

    }

    public static void main(String[] args) {
        for(int i = 0; i < 100; i++) {
            VolatileTest t = new VolatileTest();
            Thread t0 = new Thread(t);
            t0.start();
        }
        System.out.println(num);
        
    }
}

这段代码的结果有可能不是 100000,有可能小于 100000。因为 num++ 并不是原子性的。

有序性

volatile 是通过禁止指令重排序来保证有序性。为了优化程序的执行效率 JVM 在编译 Java 代码的时候或者 CPU 在执行 JVM 字节码的时候,不影响最终结果的前提下会对指令进行重新排序。

编译器会根据以下策略将内存屏障插入到指令中,禁止重排序:

  1. 在 volatile 写操作之前插入 StoreStore 屏障。禁止和 StoreStore 屏障之前的普通写操作不会进行重排序。
  2. 在 volatile 写操作之后插入 StoreLoad 屏障。禁止和 StoreLoad 屏障之后的 volatile 读写重排序。
  3. 在 volatile 读操作之后插入 LoadLoad 屏障。禁止和 LoadLoad 之后的普通读和 volatile 读重排序。
  4. 在 volatile 写操作之后插入 LoadStore 屏障。禁止和 LoadStore 屏障之后的普通写操作重排序。
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内存
    +关注

    关注

    8

    文章

    2995

    浏览量

    73847
  • JAVA
    +关注

    关注

    19

    文章

    2954

    浏览量

    104513
  • 模型
    +关注

    关注

    1

    文章

    3143

    浏览量

    48678
  • volatile
    +关注

    关注

    0

    文章

    44

    浏览量

    13003
收藏 人收藏

    评论

    相关推荐

    什么是volatile

    00. 目录文章目录00. 目录01. volatile概述02. volatile应用场景03. volatile应用示例04. 嵌入式系统中应用05. volatile官方说明
    发表于 10-28 09:23

    c语言volatile的作用

    volatile,则编译器会逐一地进行编译并产生相应的机器代码(产生四条代码)。volatile变量有两个作用:一个是告诉编译器不要进行优化;另一个是告诉系统始终从内存中取变量的地址,而不是从缓存中取变量的值(加volatile
    发表于 11-03 09:13 2399次阅读
    c语言<b class='flag-5'>volatile</b>的作用

    Volatile与多线程的认识与理解

    volatile是一个类型修饰符(type specifier),就像大家更熟悉的const一样,它是被设计用来修饰被不同线程访问和修改的变量。volatile的作用是作为指令关键字,确保本条指令
    发表于 12-01 10:31 1643次阅读

    java之用volatile和不用volatile的区别

    volatile是一个类型修饰符(type specifier),就像大家更熟悉的const一样,它是被设计用来修饰被不同线程访问和修改的变量。Java具有简单性、面向对象、分布式、健壮性、安全性、平台独立与可移植性、多线程、动态性等特点。
    发表于 12-01 10:52 3479次阅读

    volatile修饰的变量的认识和理解

     谈到volatile,理解原子性和易变性是不同的概念这一点很重要,volatile是轻量级的锁,它只具备可见性,但没有原子特性。如果你将一个域声明为volatile,那么只要对这个域产生了写操作
    发表于 12-01 11:36 5692次阅读
    <b class='flag-5'>volatile</b>修饰的变量的认识和理解

    Java中volatile的作用以及用法

    Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。
    发表于 12-01 12:14 6999次阅读

    volatile变量定义的意义和该用在哪里

    volatile 影响编译器编译的结果,volatile指出 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错
    发表于 03-07 15:29 3654次阅读
    <b class='flag-5'>volatile</b>变量定义的意义和该用在哪里

    C语言类型修饰符Volatile的使用说明

    C语言是我们经常需要用到的语言,C语言中的类型修饰符Volatile大家知道怎么使用吗? volatile是一个类型修饰符(type specifier).volatile的作用是作为指令关键字
    的头像 发表于 09-19 10:54 3519次阅读

    volatile有哪些使用误区

    在建立编译环境的时候用typedef定义了指向volatile 单元的指针,最后终于发现行不通。
    发表于 08-06 17:34 0次下载
    <b class='flag-5'>volatile</b>有哪些使用误区

    如何使用C++语法中的volatile

    volatile volatile int i = 10; volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统、硬件、其它线程等)更改。所以
    的头像 发表于 09-09 09:38 1464次阅读

    C++基础语法之volatile、assert()和sizeof()

    volatile volatile int i = 10; volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统、硬件、其它线程等)更改。所以
    的头像 发表于 09-09 09:48 1278次阅读

    【嵌入式】C语言中volatile关键字

    00. 目录文章目录00. 目录01. volatile概述02. volatile应用场景03. volatile应用示例04. 嵌入式系统中应用05. volatile官方说明
    发表于 10-21 10:21 6次下载
    【嵌入式】C语言中<b class='flag-5'>volatile</b>关键字

    C语言中的volatile是什么

    学C语言时有一个奇怪的关键字volatile,这到底有什么用呢?
    的头像 发表于 02-17 14:29 1196次阅读
    C语言中的<b class='flag-5'>volatile</b>是什么

    volatile的实现原理分析

    `volatile`是一个轻量级的`synchronized`,一般作用于 **变量** ,在多处理器开发的过程中保证了内存的可见性。相比于`synchronized`关键字,`volatile`关键字的执行成本更低,效率更高
    的头像 发表于 05-11 17:33 621次阅读
    <b class='flag-5'>volatile</b>的实现原理分析

    介绍下volatile的底层原理

    线程安全的三大特性,原子性、可见性、有序性,这三大特性与我们之前整理的内容息息相关。本篇重点介绍下volatile的底层原理,帮助我们更好的理解java并发包。
    的头像 发表于 06-09 16:17 770次阅读
    介绍下<b class='flag-5'>volatile</b>的底层原理