volatile的作用

news/2024/10/20 3:15:21/

        volatile是用来修饰成员变量的,它的作用有两个:保证变量的修改在多线程之间的可见性、禁止指令重排。

volatile的内存可见性保证

        在java内存模型中,变量都是保存在主内存中的,主内存是一块儿公共的内存区域,所有的线程都可以访问它,但是如果线程想要对变量做出修改,就只能将这个变量从主内存copy到自己的工作内存中,去修改自己工作内存中的变量副本,因为线程是无法直接读写主内存的,而工作内存是被线程私有的,任何线程都无法直接去访问其他线程的工作内存,所以当线程对一个变量做出修改之后,这个修改要被写回主内存之后,才有可能被其他线程读到,而如果我们没有用volatile关键字修饰变量,那变量的这个修改并不是被立即写回主内存的,工作内存写主内存是有时机的。

        工作内存写主内存的时机包括:

        1、当退出synchronized块儿并释放锁时;

        2、当把一个从执行引擎接收到的值赋给工作内存中的变量时;

        3、当线程结束执行时。

        所以,非volatile变量被某个线程修改之后,这个修改并不会被立即写回主内存,即便是修改被写回了主内存,这个修改也不一定会被其他线程读到,因为线程在读取共享变量时,并非每一次都去读主内存中的新值,线程在初次读取共享变量时,会将其copy到自己的工作内存中,后续的读取读的是工作内存中的变量副本,而变量副本的更新也是有时机的。

        工作内存的刷新时机包括:

        1、当线程获取到锁,进入synchronized块儿时;

        2、当线程完成了一次上下文切换重新执行时:比如,线程之前因为执行了Thread.sleep或者锁对象的wait方法而进入阻塞状态,后续苏醒或者被唤醒之后重新被cpu调度而执行时,会将主内存中变量的新值刷新到工作内存中;

        3、当执行此线程的cpu内核空闲时

        4、当然,线程在初次读取变量时,因为工作内存中还没有保存副本,所以也要从主内存往工作内存中刷新。

        总之,非volatile变量被修改之后,这个修改并不会被立即写回主内存,线程读取变量时,也不是每次读取都将主内存的新值刷新到工作内存,也就是,非volatile变量的修改在多线程之间并不是立即可见的。而我们的业务需求有的时候是要求保证变量修改在多线程之间的立即可见性的,这个时候就可以用volatile来解决问题了。

        volatile变量在被修改之后,会被立即写回主内存,并且线程在读取volatile变量时,每次读取都会去主内存刷新新值到工作内存,因此,volatile变量的修改在多线程之间是立即可见的。

volatile的禁止指令重排

        volatile的第二个作用就是禁止指令重排,为了提高程序的执行性能,编译器会对指令进行重排序优化,指令重排有两个前提:

        1、指令重排不能影响单线程的执行结果

        2、存在依赖关系的指令不允许重排

虽然指令重排不会改变单线程的执行结果,但是会破坏多线程的执行语义。所以在多线程场景下,指令重排有时候会带来线程安全问题,最经典的例子就是双重判断加锁的懒汉式单例模式:

public class Singleton {private static volatile Singleton instance;private Singleton(){}public static Singleton getInstance(){if(null == instance){synchronized (Singleton.class){if(null == instance)instance = new Singleton();}}return instance;}
}

        当我们没有用volatile关键字去修饰成员变量instance时,在第一个执行getInstance方法的线程获取到锁,进入执行instance = new Singleton();时,这虽然看似只是一行代码,但在编译后它包含着三个操作:1、为将要创建的对象实例分配内存空间;2、初始化这个对象;3、将内存地址赋给instance变量。在没有用volatile关键字去修饰instance时,这三个操作将有可能被重排序,而排序后的结果可能变成了132,也就是,先为对象分配内存,然后就将内存地址赋给了instance变量,最后才初始化对象,而在第二步将内存地址赋给instance变量之后,这个instance变量就不为空了,这时如果有另外一个线程来执行getInstance方法,那么它在经过判断之后得到Instance != null,那么这个线程就会拿到instance所指向的内存中的对象,而这个对象可能只被初始化了一半,而用一个只被初始化了一半的对象来执行接下来的业务操作,可能会得到一个错误的执行结果。所以有的时候我们需要去禁止指令重排,而禁止的方案就是在变量上加volatile关键字。       

        volatile禁止指令重排的底层原理是内存屏障,通过在需要保证执行顺序的指令间插入内存屏障能禁止指令重新排序,内存屏障保证的是:当执行到屏障前面的指令时,屏障后面的指令还未被执行;而当执行到屏障后面的指令时,屏障前面的指令已经执行完成。

volatile无法保证原子性

        虽然volatile能保证变量修改在多线程之间的可见性,以及通过禁止指令重排保证了指令执行的有序性,但是无法保证变量操作的原子性,因此它无法做出线程安全保证。比如,当我们用volatile来修饰一个int类型的变量时,这个int变量的++操作仍然是线程不安全的,若要保证共享变量的线程安全,还是得通过加锁的方式来实现。


http://www.ppmy.cn/news/1342332.html

相关文章

Mysql运维篇(四) MySQL常用命令

一路走来,所有遇到的人,帮助过我的、伤害过我的都是朋友,没有一个是敌人。如有侵权,请留言,我及时删除! 一、MySQL命令速查表 https://www.cnblogs.com/pyng/p/15560059.html Mysql DBA运维命令大全 - 墨…

python爬虫5

1.selenium交互 无页面浏览器速度更快 #配置好的自己不用管 from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionschrome_options Options()chrome_options.add_argument(‐‐headless)chrome_options.add_argument(‐‐disable‐gpu)# path…

EasyX图形库学习(二、文字输出)

目录 一、文字绘制函数 字体属性结构体:logfont 文字输出 outtextxy 在指定位置输出字符串。 ​编辑 但如果直接使用,可能有以下报错: 三种解决方案: 将一个int类型的分数,输出到图形界面上 如果直接使用: 会把score输入进去根据A…

架构篇32:可扩展架构的基本思想和模式

文章目录 前言可扩展的基本思想可扩展方式小结前言 软件系统与硬件和建筑系统最大的差异在于软件是可扩展的,一个硬件生产出来后就不会再进行改变、一个建筑完工后也不会再改变其整体结构。 例如,一颗 CPU 生产出来后装到一台 PC 机上,不会再返回工厂进行加工以增加新的功…

signalR+websocket:实现消息实时通讯——技能提升

signalR 解决步骤1:npm install microsoft/signalr6.0.6 安装指定版本的microsoft/signalr,我这边安装的版本是6.0.6 解决步骤2:引入import * as signalR from microsoft/signalr; import * as signalR from microsoft/signalr; 下面第三…

会声会影2024破解版如何下载?

要下载会声会影2024,您可以按照以下步骤进行操作: 会声会影2024安装包下载如下: https://wm.makeding.com/iclk/?zoneid55677 1. 访问会声会影官方网站:在您的网络浏览器中,输入"会声会影2024官方网站"进行搜索&…

图论练习4

内容:染色划分,带权并查集,扩展并查集 Arpa’s overnight party and Mehrdad’s silent entering 题目链接 题目大意 个点围成一圈,分为对,对内两点不同染色同时,相邻3个点之间必须有两个点不同染色问构…

【DDD】学习笔记-什么是模型

从领域驱动的战略设计进入战术设计,简单说来,就是跨过系统视角的限界上下文边界进入它的内部,从分层架构的逻辑分层进入到每一层的内部。在思考内部的设计细节时,首先需要思考的问题就是:什么是模型(Model&…