首页 > 其他分享 >volatile关键字

volatile关键字

时间:2024-05-07 23:24:41浏览次数:16  
标签:指令 关键字 屏障 线程 内存 操作 volatile

volatile关键字

概要

volatile修饰符并不是Java语言的首创,早在C和C++当中就已经存在。为了理解volatile关键字的作用和原理,需要先了解一些计算机基础知识。请先参考《什么是Java内存模型(JMM)?》


我们知道,并发编程时,线程安全涉及三个特性:原子性、可见性、有序性。volatile用于保证修饰变量的可见性、有序性,但是不能保证原子性。

 

一、 可见性

当访问共享变量的多个线程运行在多核CPU上时,可能会出现可见性问题。synchronized关键字和lock可以解决这个问题,但是会阻塞线程,降低性能,所以java给出了更轻量级关键字volatile,不会阻塞线程。

1. volatile 可见性实现
保证变量在线程间的可见性。可见性的保证是基于CPU的内存屏障指令,被JSR-133抽象为happens-before原则。

volatile 变量的内存可见性是基于内存屏障(Memory Barrier)实现

内存屏障:
内存屏障也称为内存栅栏或栅栏指令,是一种屏障指令,它使CPU或编译器对屏障指令之前和之后发出的内存操作执行一个排序约束。 这通常意味着在屏障之前发布的操作被保证在屏障之后发布的操作之前执行。


1. 这里的可见性是什么意思?
当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。
volatile关键字有这样的特性得益于java语言的先行发生原则(happens-before)

在计算机科学中,先行发生原则是两个事件的结果之间的关系,如果一个事件发生在另一个事件之前,结果必须反映,即使这些事件实际上是乱序执行的(通常是优化程序流程)
这里所谓的事件,实际上就是各种指令操作,比如读操作、写操作、初始化操作、锁操作等等。

 

先行发生原则作用于很多场景下,包括同步锁、线程启动、线程终止、volatile。
我们这里只列举出volatile相关的规则:对于一个volatile变量的写操作先行发生于后面对这个变量的读操作。

二、有序性


保证有序性,阻止指令重排。编译时JVM编译器遵循内存屏障的约束,运行时依靠CPU屏障指令来阻止重排。

1. 什么是指令重排
指令重排是指JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对现有的指令顺序进行重新排序。简单来说就是系统在执行代码的时候并不一定是按照代码顺序依次执行。

2. 指令重排的目的
指令重排的目的是为了在不改变程序执行结果的前提下,优化程序的运行效率。需要注意的是,这里所说的不改变执行结果,指的是不改变单线程下的程序执行结果。
Java 源代码会经历 编译器优化重排 —> 指令并行重排 —> 内存系统重排 的过程,最终才变成操作系统可执行的指令序列。指令重排序可以保证串行语义一致,但是没有义务保证多线程间的语义也一致 ,所以在多线程下,指令重排序可能会导致一些问题。


内存屏障共分为四种类型:

LoadLoad屏障:

抽象场景:Load1; LoadLoad; Load2

Load1 和 Load2 代表两条读取指令。在Load2要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
说明:禁止下面所有的普通读操作和上面的 volatile 读重排序。


StoreStore屏障:

抽象场景:Store1; StoreStore; Store2

Store1 和 Store2代表两条写入指令。在Store2写入执行前,保证Store1的写入操作对其它处理器可见

说明:禁止上面的普通写和下面的 volatile 写重排序。
由于StoreStore屏障保障上面所有的普通写在volatile写之前刷新到主内存,StoreStore屏障可以保证在volaitle写之前,其前面的所有普通写操作已经对任意处理器可见了。


LoadStore屏障:

抽象场景:Load1; LoadStore; Store2

在Store2被写入前,保证Load1要读取的数据被读取完毕。
说明:禁止下面所有的普通写操作和上面的 volatile 读重排序。


StoreLoad屏障:

抽象场景:Store1; StoreLoad; Load2

在Load2读取操作执行前,保证Store1的写入对所有处理器可见。StoreLoad屏障的开销是四种屏障中最大的。
说明:防止上面的 volatile 写与下面可能有的 volatile 读/写重排序。

在一个变量被volatile修饰后,JVM会为我们做两件事:


1.在每个volatile写操作前插入StoreStore屏障,在写操作后插入StoreLoad屏障。

2.在每个volatile读操作前插入LoadLoad屏障,在读操作后插入LoadStore屏障。


三、解决了long类型和double类型数据的8字节赋值问题

volatile除了保证可见性和阻止指令重排,还解决了long类型和double类型数据的8字节赋值问题。

在Java中,long类型和double类型的赋值不是原子操作,它们的赋值过程需要分两步完成:先将高32位写入内存,再将低32位写入内存。这种情况下,如果一个线程在赋值的过程中被另一个线程打断,可能会导致某个线程读取到了部分更新后的值,而另一部分还是旧值,从而出现数据不一致的情况。


四、volatile使用场景
1.运行结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。

2.变量不需要与其他的状态变量共同参与不变约束。

五、volatile的相关总结

1. volatile是轻量级同步机制
在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,是一种比synchronized关键字更轻量级的同步机制
2. 不能替代synchronized和加锁机制
volatile只能保证内存可见性,不能保证原子性,所以不能替代synchronized和加锁机制。后两种机制既可以确保可见性又可以确保原子性。
3. 效率比较低
volatile频繁从内存中读写,且屏蔽掉了JVM中必要的代码优化,和普通变量比较,效率上比较低,因此一定在必要时才使用此关键字

 

 

 

标签:指令,关键字,屏障,线程,内存,操作,volatile
From: https://www.cnblogs.com/hld123/p/18178674

相关文章

  • [转帖]【MySQL】字段名与关键字冲突解决办法
    https://www.jianshu.com/p/50e59feb3e83   首先,不推荐使用MySQL的关键词来作为字段名,但是有时候的确没有注意,或者因为之前就这么写了,没办法,那怎么办呢?方法1,改字段名,改了肯定就没问题了。这个就不细说了。方法2,使用引号`来处理。  下面就详细的说明一下怎样使用方法......
  • 使用快捷键的方式把多个关键字文本快速替换(快速替换AE脚本代码)
     首先,需要用到的这个工具:度娘网盘提取码:qwu2蓝奏云提取码:2r1z这里做AE(AdobeAfterEffact)里的脚本规则,把英文替换成中文,如下swap=thisComp.layer(“Segmentsettings”).effect("%")(“Checkbox”);if(swap==true){s=thisComp.layer(“Segmentsettings”).effect(“Se......
  • VScode自定义折叠代码快 region和endregion 关键字
    前言全局说明VScode自定义折叠代码快region和endregion关键字一、说明vscode有自带的代码折叠功能,但是因为某些内容不是标准的代码或不被识别就不能正常被折叠比如很多的单行注释,或者被注释的代码就能不能自动折叠。这里就要用到region和endregion关键字使用时r......
  • js设置网页标题、关键字、描述
    import.meta.env.VITE...Vue.js3.x获取环境变量letdocTitle=import.meta.env.VITE_TITLE;letdocDesc=import.meta.env.VITE_DESCRIPT;letdocKeywords=import.meta.env.VITE_KEYWORDS;//设置页面标题document.title=docTitle;//设置页......
  • TMemo 关键字代码 着色
    PosLabel:TLabel;是显示行列号unitUnit1;interfaceusesWindows,Messages,SysUtils,Classes,Graphics,Controls,Forms,Dialogs,StdCtrls,ComCtrls;type//InterjectedClassTMemo=class(stdctrls.TMemo)privateprocedureWMPaint(varMe......
  • Java并发01---JMM模型、Volatile、CAS操作、自旋锁、ABA问题
    @目录JMM(JavaMemoryModel)Volatile修饰CAS(CompareAndSwap)ABA问题JMM(JavaMemoryModel)首先要明确的是JMM与JVM内存结构不是同一个概念,记的时候不要记混。我们先来回顾一下JVM内存结构,其包括了堆、方法区、虚拟机栈、程序计数器、本地方法区,其中前二者为线程共享,后三者为线程......
  • JavaScript精粹:26个关键字深度解析,编写高质量代码的秘诀!
    JavaScript关键字是一种特殊的标识符,它们在语言中有固定的含义,不能用作变量名或函数名。这些关键字是JavaScript的基础,理解它们是掌握JavaScript的关键。今天,我们将一起探索JavaScript中的26个关键字,了解这些关键字各自独特的含义、特性和使用方法。一、JavaScript关键字是什么......
  • 查找指定目录下所有子目录文件中是否存在关键字
    #-*-coding:gb18030-*-importosimportioimportsysreload(sys)sys.setdefaultencoding('utf-8')path=r'D:\k1_total_quantity_scripts\Suites\Onetrack\DesignVaildationTEST\FeatureTEST\BSPTEST\格式化'forroot,_,filesi......
  • Java面试题:请谈谈Java中的volatile关键字?
    在Java中,volatile关键字是一种特殊的修饰符,用于确保多线程环境下的变量可见性和顺序性。当一个变量被声明为volatile时,它可以确保以下两点:内存可见性:当一个线程修改了一个volatile变量的值,其他线程会立即看到这个改变。这是因为volatile关键字会禁止CPU缓存和编译器优化,从而确......
  • Java中的static关键字解析
    一.static关键字的用途二.static关键字的误区三.常见的笔试面试题一.static关键字的用途在《Java编程思想》P86页有这样一段话:“static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,......