前言:volatile关键字在面试中经常被问到,从volatile关键字可以引申出许多知识点,因此有必要对此进行总结。本文根据《深入理解Java虚拟机——JVM高级特性与最佳实践》中的相关章节,整理得来。


相关名词

TPS(Transactions Per Second):每秒事务处理数,衡量一个服务性能好坏的评判标准。

JMM(Java Memory Model):Java内存模型。

1.硬件上解决数据一致性

由于CPU有高速缓存机制,所以在程序运行时,会将需运算的数据从主存中复制一份到高速缓存中,在CPU进行计算时,直接在缓存中进行数据的读取和写入,在运算完成后,再将缓存中的数据刷新到主存中。这种模式在单线程中是没有任何问题的,但在多线程中会出现数据不一致的问题,如i++问题。对变量的操作涉及三个步骤:读取值、操作值和重新写入新值,这就是在多线程出现数据不一致的原因。

解决方式:

①在总线上加LOCK锁的方式

在运算时,直接在总线上加锁,可以避免缓存不一致问题,但是CPU利用率极低。

②缓存一致协议,参考:https://www.cnblogs.com/yjf512/p/5166415.html

2.JMM内存模型

JMM:用来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。

JMM主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量在主存和从内存中如何取出的规则。此处的变量包括实例字段、静态字段和构成数组对象的元素,不包括局部变量和方法参数(线程私有)。

JMM中规定变量都存在于主存中,线程都有自己的工作内存,线程对变量(是对主存中变量的副本拷贝)的操作必须在工作内存中,而不能直接对主存中数据进行操作,并且每个线程不能访问其他线程的工作内存,线程间变量值的传递需要通过主内存来完成。也就是说线程在工作内存中进行数据的操作,然后再更新主存中的值。因此在多线程中可能出现i++问题。

对于主内存与工作内存中变量之间的交互,JMM中定义了8种操作来完成,这8种操作具有原子性。

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

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

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

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

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

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

⑦store(存储):作用于工作内存的变量,它把工作内存中的一个变量的值传递到主内存中,以便随后的write操作使用。(工作在从存,传输数据到主存)

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

注:从主内存到工作内存,需顺序执行read和load,从工作内存到主内存,需顺序执行store和wirte操作,JMM只要求这些操作是顺序执行的,并不保证连续执行。

要保证并发的正确执行,就需要保证原子性、可见性和有序性。只要有一个条件未满足,就可能导致程序运行结果不正确。

在JMM中是如何保证并发执行的正确性的,有如下三点:

①JMM只保证基本的读取和赋值是原子性操作,如i=10;如果需要更大范围的原子性,则需要synchronized和Lock的帮助了。

②可见性:由volatile关键字提供,当然通过synchronized和Lock也可以实现。

③有序性:JMM中具备一些先天的“有序性”(happens-before原则),通过volatile也可保证一定的有序性,当然通过synchronized和Lock也可保证有序性

3.volatile关键字

被volatile修饰的变量,具有两种特性:

①保证变量对所有线程是可见的,当一条线程修改了变量的值时,新值对于其他线程来说是可以立即得知的。

对变量进行修改涉及两个操作:a.修改线程工作内存中的值;b.将修改后的值更新到主内存。

当A线程对volatile变量的值修改时,会导致其他线程中的变量缓存无效,所以其他线程再次读取volatile变量的值时,会从主存中获取。

②禁止指令重排序优化

指令重排序优化是指处理器为了提高程序运行的效率,可能会对输入的代码进行优化,它不保证代码的执行顺序与书写顺序一致,但保证程序的最终执行结果和书写顺序的结果一致。指令

volatile能在一定程度上保证有序性,为什么说一定程度上看下面解释。

禁止指令重排序优化有两层意思:

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

从该点可以看出volatile只能在一定程度上保证有序性:A->volatile->B,整体上按照这种顺序执行,但是在A操作里还是可能存在指令重排序,只要其操作对后续操作无影响。

b.在进行指令优化时,不能将volatile变量的语句放在volatile位置之前执行,也不能把volatile变量语句放到volatile语句之后执行。可能这句话有点难以理解,通过下面伪代码简单说明。

 //a、b为非volatile变量
//flag为volatile变量 a = 1; //语句1
b = 2; //语句2
flag = true; //语句3
a = 3; //语句4
b = 4; //语句5

因为flag为volatile变量,所以执行指令重排序优化时,不能将语句3放在在语句1、2的前面;也不能将语句3放在语句4、5的后面执行。并且执行到语句3时,语句1、2已经执行完毕,并且其结果对语句3、4、5是可见的。注意语句1、2和语句4、5的顺序不做任何保证(这里也说明了volatile只能在一定程度上保证有序性)。

volatile的两种特性,主要由内存屏障提供(volatile原理和实现机制)。

加入volatile关键字时,会多出一个lock前缀指令(反编译),该lock前缀指令实际上相当于一个内存屏障,内存屏障会提供3个功能:

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

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

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

volatile不能保证原子性,其原因可通过i++操作来分析:

i初始值为0,并且被volatile修饰,当线程A读取i后,还没对i进行自增操作时被阻塞,此时线程B进来,虽然volatile保证了线程B是从主存中读取的数据,但是由于线程A并未对i值进行修改,所以线程B不会发现修改后的i值,这就是volatile不保证原子性的根源。

volatile的使用场景:

①对变量的写操作不依赖于当前值。如i++场景,就不能使用volatile。

②该变量没有包含在具有其他变量的不变式中。通俗理解:就是volatile变量不参加与其他变量的共同计算。

4.happens-before原则

happens-before(先行发生原则)主要是用来保证JMM的先天“有序性”,如果两个操作的执行顺序不能遵循happens-before原则,则虚拟机就可以对其进行重排序优化。

①程序次序规则:一个线程内,按照代码顺序,前面的操作对后面的操作先行发生。(主要保证单线程执行结果的正确性)

②锁定规则:一个unLock操作后,才能对同一个锁Lock操作,也就说对于同一锁,需要先unLock,其次才进行Lock。

③volatile变量规则:对一个变量的写操作对后面对这个变量的读操作先行发生。

④传递规则:如果操作A对操作B先行发生,操作B对操作C也先行发生,则可以得出操作A对操作C先行发生。(传递性)

⑤线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作。

⑥线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。

⑦线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。

⑧对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始。

对于happens-before原则,前四条非常重要。

总结:

volatile关键字引申出来的知识点非常多,笔者认为主要有以下几点:

①硬件层面上解决数据一致性的方式(两种)。

②JMM的相关知识,主要理解主内存(主存)和工作内存,以及read和load、store和write这两对操作。

③在JMM中如何保证并发程序执行的正确性的(原子性、可见性(volatile)、有序性(happens-before原则))。

④volatile的底层原理:内存屏障。


by Shawn Chen,2018.6.4日,上午。

整理volatile相关知识点的更多相关文章

  1. Caffe学习系列(二)Caffe代码结构梳理,及相关知识点归纳

    前言: 通过检索论文.书籍.博客,继续学习Caffe,千里之行始于足下,继续努力.将自己学到的一些东西记录下来,方便日后的整理. 正文: 1.代码结构梳理 在终端下运行如下命令,可以查看caffe代码 ...

  2. php正则相关知识点

    关于正则,其实简单就是搜索和匹配.php,java,python等都是支持正则的,php正则兼容perl.好多同学觉得正则比较难,比较抽象,其实正则是非常简单的,主要是一个熟悉和反复练习的结果,还有一 ...

  3. UITableView相关知识点

    //*****UITableView相关知识点*****// 1 #import "ViewController.h" // step1 要实现UITableViewDataSou ...

  4. Android开发涉及有点概念&相关知识点(待写)

    前言,承接之前的 IOS开发涉及有点概念&相关知识点,这次归纳的是Android开发相关,好废话不说了.. 先声明下,Android开发涉及概念比IOS杂很多,可能有很多都题不到的.. 首先由 ...

  5. IOS开发涉及有点概念&相关知识点

    前言,IOS是基于UNIX的,用C/C+/OC直通系统底层,不想android有个jvm. 首先还是系统架构的分层架构 1.核心操作系统层 Core OS,就是内存管理.文件系统.电源管理等 2.核心 ...

  6. IOS之UI--小实例项目--添加商品和商品名(使用xib文件终结版) + xib相关知识点总结

    添加商品和商品名小项目(使用xib文件终结版) 小贴士:博文末尾有项目源码在百度云备份的下载链接. xib相关知识点总结 01-基本使用 一开始使用xib的时候,如果要使用自定义view的代码,就需要 ...

  7. 学习记录013-NFS相关知识点

    一.NFS相关知识点 1.NFS常用的路径/etc/exports NFS服务主配置文件,配置NFS具体共享服务的地点/usr/sbin/exportfs NFS服务的管理命令,exportfs -a ...

  8. TCP/IP 相关知识点与面试题集

    第一部分:TCP/IP相关知识点 对TCP/IP的整体认 链路层知识点 IP层知识点 运输层知识点 应用层知识点 (这些知识点都可以参考:http://www.cnblogs.com/newwy/p/ ...

  9. Python开发一个csv比较功能相关知识点汇总及demo

    Python 2.7 csv.reader(csvfile, dialect='excel', **fmtparams)的一个坑:csvfile被csv.reader生成的iterator,在遍历每二 ...

随机推荐

  1. webapi 控制json的字段(key)显示顺序

    使用两个c#的特性: 加在类上的:[DataContract] 加在字段上的:[DataMember(Name = "ResultCode",EmitDefaultValue = ...

  2. 去重分页sql语句

    ---恢复内容开始--- SELECT [TAGNAME] FROM ( SELECT tagname,ROW_NUMBER() OVER(order by tagname) AS RowNumber ...

  3. Docker多步构建更小的Java镜像

    译者按: 最新版Docker将支持多步构建(Multi-stage build),这样使用单个Dockerfile就可以定义多个中间镜像用于构建,测试以及发布等多个步骤,并且有效减小最终镜像的大小. ...

  4. html/css的学习之路(2)

    今天我跟大家说一下html,html里面其实什么东西都可以放进去的.首先创建一个文本,改变他的后缀名为html.这时有个最重要的一点不管是html还是css都不可以用中文和符号命名. 这是html中的 ...

  5. Linux配置防火墙端口 8080端口

    1.查看防火墙状态,哪些端口开放了 /etc/init.d/iptables status 2.配置防火墙 vi /etc/sysconfig/iptables   ################# ...

  6. 洛谷P3178 [HAOI2015]树上操作

    题目描述 有一棵点数为 N 的树,以点 1 为根,且树点有边权.然后有 M 个操作,分为三种:操作 1 :把某个节点 x 的点权增加 a .操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 ...

  7. cf1132G. Greedy Subsequences(线段树)

    题意 题目链接 Sol 昨天没想到真是有点可惜了. 我们考虑每个点作为最大值的贡献,首先预处理出每个位置\(i\)左边第一个比他大的数\(l\),显然\([l + 1, i]\)内的数的后继要么是\( ...

  8. .net webapi 后台导出excel 申请付款单实例

    [HttpGet, AllowAnonymous] public void ExportSettlementPrint(string code) { FinSettlementModel settle ...

  9. java I/O工作机制

    java I/O 的基本架构: 1:基于字节操作的I/O接口 InputStream OutputStream 2:基于字符操作的I/O接口 Writer 和Reader 3:基于磁盘操作的I/O接口 ...

  10. Android 如何解决dialog弹出时无法捕捉Activity的back事件

    Android 如何解决dialog弹出时无法捕捉Activity的back事件 在一些情况下,我们需要捕捉back键事件,然后在捕捉到的事件里写入我们需要进行的处理,通常可以采用下面三种办法捕捉到b ...