在了解valotile关键字之前。我们先来了解其他相关概念。

1.1  java内存模型:

不同的平台,内存模型是不一样的,我们可以把内存模型理解为在特定操作协议下,对特定的内存或高速缓存进行读写访问的过程抽象。Java 虚拟机规范中试图定义一种 Java 内存模型(Java Memory Model,简称 JMM)来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果,不必因为不同平台上的物理机的内存模型的差异,对各平台定制化开发程序。

更具体一点说,Java 内存模型提出目标在于,定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。

1.2  Java 内存模型的组成

主内存

Java 内存模型规定了所有变量都存储在主内存(Main Memory)中(此处的主内存与介绍物理硬件的主内存名字一样,两者可以互相类比,但此处仅是虚拟机内存的一部分)。

工作内存

每条线程都有自己的工作内存(Working Memory,又称本地内存.),线程的工作内存中保存了该线程使用到的变量的主内存中的共享变量的副本拷贝。

(工作内存是 JMM 的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。)

JAVA内存模型抽象示意图:

线程执行的时候,将首先从主内存读值,再load到工作内存中的副本中,然后传给处理器执行,执行完毕后再给工作内存中的副本赋值,随后工作内存再把值传回给主存,主存中的值才更新。

在这个过程中如果出现多个线程同时在处理这些值,岂不是会出现并发问题?

1.3 Java 内存间的交互操作

关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,Java 内存模型中定义了下面 8 种操作来完成。

虚拟机实现时必须保证下面介绍的每种操作都是原子的,不可再分的(对于 double 和 long 型的变量来说,load、store、read、和 write 操作在某些平台上允许有例外)。

8 种基本操作,如下图:

lock (锁定) , 作用于主内存的变量,它把一个变量标识为一条线程独占的状态。

unlock (解锁) , 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

read (读取) , 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用。

load (载入) , 作用于工作内存的变量,它把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。

use (使用) , 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时就会执行这个操作。

assign (赋值) , 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。

store (存储) , 作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后 write 操作使用。

write (写入) , 作用于主内存的变量,它把 Store 操作从工作内存中得到的变量的值放入主内存的变量中。

Java 内存模型运行规则

内存交互基本操作的 3 个特性

Java 内存模型是围绕着在并发过程中如何处理这 3 个特性来建立的.

原子性(Atomicity)

原子性,即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

即使在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所干扰。

可见性(Visibility)

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

正如上面“交互操作流程”中所说明的一样,JMM 是通过在线程 1 变量工作内存修改后将新值同步回主内存,线程 2 在变量读取前从主内存刷新变量值,这种依赖主内存作为传递媒介的方式来实现可见性。

有序性(Ordering)

在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性

Valotile的作用?

1)保证可见性

valotile本意是不稳定的,易挥发的,也就是说,用它修饰的变量是可变的。

结合我们的Java内存模型介绍,我们可以了解在多线程环境下,线程会将线程间共享的变量保存在本地内存中,而不是从内存中读取,这就可能会引发不一致的问题,另一个进程可能在此线程运行期间改变了变量的值,而此线程并没有看到变化。

而volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。

也就是说,JVM会对线程变量的访问进行优化,这样当多个线程同时与某个对象时交互,就必须要注意到要让线程及时的得到共享成员变量的变化。

而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。

使用valotile前后示意图:

从上图可以直观的看到valotile的实现可见性的原理,线程对变量读取的时候,直接从主内存中读,而不是从线程的工作内存。也就避免了其他线程操作时修改了变量的值。

2)禁止进行指令重排序

为了使得处理器内部的运算单元尽量被充分利用,提高运算效率,处理器可能会对输入的代码进行排序。但是可能出现如下情况:

正常情况下,逻辑 A 执行完之后再执行逻辑 B。在处理器乱序执行优化情况下,有可能导致 flag 提前被设置为 true,导致逻辑 B 先于逻辑 A 执行。

这个时候volatile又起到了作用。

当程序执行到 volatile 变量的读操作或者写操作时, 在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行。

在进行指令优化时, 不能将在对 volatile 变量访问的语句放在其后面执行,也不能把 volatile 变量后面的语句放到其前面执行。

(普通的变量仅仅会保证该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证赋值操作的顺序与程序代码中的执行顺序一致)

实现原理:

内存屏障会提供3个功能:

  1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

  2)它会强制将对缓存的修改操作立即写入主存;

  3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

总结:1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

   2)禁止进行指令重排序。

Valotile关键字详解的更多相关文章

  1. 【转载】C/C++中extern关键字详解

    1 基本解释:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义.此外extern也可用来进行链接指定. 也就是说extern ...

  2. python关键字详解

    今天依旧在啃:<笨方法学python>,其中习题37是复习各种关键字.我本想百度一下记一下就ok了,但是百度出来第一个就Hongten的博客.我才意识到我也有博客,我应该学习他,把这些积累 ...

  3. Java面试题04-final关键字详解

    Java面试题04-final关键字详解 本篇博客将会讨论java中final关键字的含义,以及final用在什么地方,感觉看书总会有一些模糊,而且解释的不是很清楚,在此做个总结,以备准备面试的时候查 ...

  4. Objective-C 实用关键字详解1「面试、工作」看我就 🐒 了 ^_^.

    在写项目 或 阅读别人的代码(一些优秀的源码)中,总能发现一些常见的关键字,随着编程经验的积累大部分还是知道是什么意思 的. 相信很多开发者跟我当初一样,只是基本的常用关键字定义属性会使用,但在关键字 ...

  5. java continue break 关键字 详解 区别 用法 标记 标签 使用 示例 联系

    本文关键词: java continue break 关键字 详解 区别  用法 标记  标签 使用 示例 联系   跳出循环 带标签的continue和break 嵌套循环  深入continue ...

  6. mysql中文、英文别名排序问题,order by 关键字详解

    order by 关键字详解:   SELECT intcode AS 商品编码, product_title AS 名称, retailprice AS 零售价, purchaseprice AS ...

  7. Java多线程(三)—— synchronized关键字详解

    一.多线程的同步 1.为什么要引入同步机制 在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源.必须对这种潜在资源冲突进行预防. 解决方法:在线程使用一个资源时为其加锁即可. 访问资 ...

  8. java关键字详解----static

    Java Static关键字详解   提起static关键字,相信大家绝对不会陌生,但是,想要完全说明白,猛的一想,发现自己好像又说不太明白... ...比方说,昨天被一个同学问起的时候... ... ...

  9. Delphi、Lazarus保留字、关键字详解

    Delphi.Lazarus保留字.关键字详解 来自橙子,万一的博客以及其他地方 保留字:变量等标识符可以再使用: 关键字:有特定含义,不能再次重新定义: 修饰字:类似保留字的功能,也就是说可以重用 ...

随机推荐

  1. Charles 的界面详解

    后续补充.......... 一.主导航栏 1.File.Edit.View.Proxy.Tools.Window.Help 2.View栏 (1)structure视图是将网络请求按访问的域名分类: ...

  2. 【actitivi】配置运行上遇到的问题

    基础: 需要 问题1:对于activiti-admin,添加mysql-connector-java-5.1.47.jar后: Sun Apr 28 16:09:00 CST 2019 WARN: E ...

  3. df=df.reset_index(drop=True)

    df=df.reset_index(drop=True) ============ df = pd.read_csv('./train_file/train.csv').dropna()df_test ...

  4. 【LeetCode每天一题】Unique Paths(唯一的路径数)

    A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below).The ...

  5. AMBER: CPPTRAJ Tutorial C0

    CPPTRAJ作为PTRAJ的继任者,拥有比PTRAJ更强大的功能,本教程会简要的介绍CPPTRAJ的用法及注意事项. 需要的文件: trpzip2.gb.nc trpzip2.ff10.mbondi ...

  6. eval() 和 int()区别,以及eval作用

    eval()方法作用是将数据转换回原本的类型 a = str(list) type(a)--->str eval(a) type(a)--->list

  7. 测试12.2.0.1RAC PDB级别的Failover

    关键步骤:手工添加服务名A并启动(已验证默认的服务名测试验证无法实现Failover) [oracle@db90 ~]$ srvctl add service -db orcl -service A ...

  8. FB面经Prepare: Friends Recommendation

    有个getFriend() API, 让你推荐你的朋友的朋友做你的朋友,当然这个新朋友不能是你原来的老朋友 package fb; import java.util.*; public class R ...

  9. Pocket Gem OA: Log Parser

    time a given player spends actually connected to the network. We keep console logs of various game s ...

  10. python导入.py文件

    1.from . import D # 导入A.B.D 2.from .. import E # 导入A.E 3.from ..F import G # 导入A.F.G,.. 和 F是连着的,中间没有 ...