Java内存模型中volatile关键字的作用
JAVA内存模型,为啥线程要有自己的本地内存,CPU高速缓存
volatile作用总结:
1. 强制线程从公共内存中取得变量的值,而不是从线程的私有的本地内存(如CPU高速缓存)中,volatile修饰的变量不具有原子性(修改一个变量的值不能同步)。
2. 保证volatile修饰的变量在被一个线程修改后,会被强制立即刷新到主存(可见性),其他线程如果有该变量的缓存行,会被设置为无效。
3. 禁止指令重排(有序性)。
a.happen-before
b.编译器在生成字节码时, 会在指令序列中插入内存屏障,会多出一个 lock 前缀指令
内存屏障是一组处理器指令,解决禁止指令重排序和内存可见性的问题,保证指令重排序后与之前的输出结果一 样,使性能得到优化。处理器在进行重排序时是会考虑指令之间 的数据依赖性。
指令重排:Java 语言规范规定了JVM线程内部维持顺序化语义,也就是说只要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。这个过程通过叫做指令的重排序。
指令重排序存在的意义在于:JVM能够根据处理器的特性(CPU的多级缓存系统、多核处理器等)适当的重新排序机器指令,使机器指令更符合CPU的执行特点,最大限度的发挥机器的性能,提高效率。
使用 volatile 的场景:
- 双重校验锁 DCL(double checked locking)
- ConcurrentHashMap的哈希数组Node[]
- 原子类例如AtomicInteger的value属性
- 多个线程操作同一块内存的同一个变量(保证有序性和内存可见性,有序性和可见性同等重要,都不能忽略)
一旦一个共享变量(类的成员变量、类的静态成员变量)被 volatile 修饰 之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行读取时的可见性,即一个线程修改 了某个变量的值,这新值对其他线程来说是立即可见的。(volatile 解决了 线程间共享变量的可见性问题)。
第一:使用 volatile 关键字会强制将修改的值立即写入主存;
第二:使用 volatile 关键字的话,当线程 2 进行修改时,会导致线程 1 的 工作内存中缓存变量 stop 的缓存行无效(反映到硬件层的话,就是 CPU 的 L1 或者 L2 缓存中对应的缓存行无效);
第三:由于线程 1 的工作内存中缓存变量 stop 的缓存行无效,所以线程 1 再次读取变量 stop 的值时会去主存读取。 那么,在线程 2 修改 stop 值时(当然这里包括 2 个操作,修改线程 2 工 作内存中的值,然后将修改后的值写入内存),会使得线程 1 的工作内存中缓 存变量 stop 的缓存行无效,然后线程 1 读取时,发现自己的缓存行无效,它会 等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。 那么线程 1 读取到的就是最新的正确的值。
2)禁止进行指令重排序,阻止编译器对代码的优化。
volatile 关键字禁止指令重排序有两层意思:
I) 当程序执行到 volatile 变量的读操作或者写操作时,在其前面的操作的 更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定 还没有进行;
II) 在进行指令优化时,不能把 volatile 变量前面的语句放在其后面执行, 也不能把 volatile 变量后面的语句放到其前面执行。 为了实现 volatile 的内存语义,加入 volatile 关键字时,编译器在生成字节码时, 会在指令序列中插入内存屏障,会多出一个 lock 前缀指令。
内存屏障,有 2 个作用:1.先于这个内存屏障的指令必须先执行,后于这个内存屏障 的指令必须后执行。2.使得内存可见性。所以,如果你的字段是 volatile,在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据。在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存。
lock 前缀指令在多核处理器下会引发了两件事情: 1.将当前处理器中这个变量所在缓存行的数据会写回到系统内存。这个写回内存的 操作会引起在其他 CPU 里缓存了该内存地址的数据无效。但是就算写回到内存,如果 其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了 保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总 线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内 存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进 行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。 2.它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面 的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全 部完成。
内存屏障可以被分为以下几种类型:
LoadLoad 屏障:对于这样的语句 Load1; LoadLoad; Load2,在 Load2 及后续读取 操作要读取的数据被访问前,保证 Load1 要读取的数据被读取完毕;
StoreStore 屏障:对于这样的语句 Store1; StoreStore; Store2,在 Store2 及后续写 入操作执行前,保证 Store1 的写入操作对其它处理器可见;
LoadStore 屏障:对于这样的语句 Load1; LoadStore; Store2,在 Store2 及后续写 入操作被刷出前,保证 Load1 要读取的数据被读取完毕;
StoreLoad 屏障:对于这样的语句 Store1; StoreLoad; Load2,在 Load2 及后续所 有读取操作执行前,保证 Store1 的写入对所有处理器可见;它的开销是四种屏障中最 大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功 能。
来源《Java并发编程实践》
happen-before:
① 程序次序法则:线程中的每个动作A都happens-before于该线程中的每一个动作B,其中,在程序中,所有的动作B都能出现在A之后。
② 监视器锁法则:对一个监视器锁的解锁 happens-before于每一个后续对同一监视器锁的加锁。
③ volatile变量法则:对volatile域的写入操作happens-before于每一个后续对同一个域的读写操作。
④ 线程启动法则:在一个线程里,对Thread.start的调用会happens-before于每个启动线程的动作。
⑤ 线程终结法则:线程中的任何动作都happens-before于其他线程检测到这个线程已经终结、或者从Thread.join调用中成功返回,或Thread.isAlive返回false。
⑥ 中断法则:一个线程调用另一个线程的interrupt happens-before于被中断的线程发现中断。
⑦ 终结法则:一个对象的构造函数的结束happens-before于这个对象finalizer的开始。
⑧ 传递性:如果A happens-before于B,且B happens-before于C,则A happens-before于C
Java内存模型中volatile关键字的作用的更多相关文章
- Java内存模型与volatile关键字
Java内存模型与volatile关键字 一).并发程序开发 并行程序的开发要涉及多线程.多任务间的协作和数据共享问题. 常用的并发控制:内部锁.重入锁.读写锁.信号量. 二).线程的特点 线程的特点 ...
- Java并发编程:JMM(Java内存模型)和volatile
1. 并发编程的3个概念 并发编程时,要想并发程序正确地执行,必须要保证原子性.可见性和有序性.只要有一个没有被保证,就有可能会导致程序运行不正确. 1.1. 原子性 原子性:即一个或多个操作要么全部 ...
- java中volatile关键字的作用
一.内存模型的相关概念 大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存 ...
- (第三章)Java内存模型(中)
一.volatile的内存语义 1.1 volatile的特性 理解volatile特性的一个好办法是把对volatile变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步.下面通过具体 ...
- Java内存模型:volatile详解
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt202 Java内存模型:volatile是干什么用的Volatile字段是用 ...
- 什么是Java内存模型中的happens-before
Java内存模型JMM Java内存模型(即Java Memory Model , 简称JMM),本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序个各个变量(包括实 ...
- 深入理解Java内存模型中的虚拟机栈
深入理解Java内存模型中的虚拟机栈 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域都会有各自的用途,以及创建和销毁的时间,有的区域会随着虚拟机进程的启 ...
- Java中volatile关键字及其作用是什么?
在 Java 多线程中如何保证线程的安全性?那我们可以使用 Synchronized 同步锁来给需要多个线程访问的代码块加锁以保证线程安全性.使用 synchronized 虽然可以解决多线程安全问题 ...
- Java 中 volatile 关键字及其作用
引言 作为 Java 初学者,几乎从未使用过 volatile 关键字.但是,在面试过程中,volatile 关键字以及其作用还是经常被面试官问及.这里给各位童靴讲解一下 volatile 关键字的作 ...
随机推荐
- [LeetCode] 216. Combination Sum III 组合之和 III
Find all possible combinations of k numbers that add up to a number n, given that only numbers from ...
- [LeetCode] 529. Minesweeper 扫雷
Let's play the minesweeper game (Wikipedia, online game)! You are given a 2D char matrix representin ...
- namespace Measure
namespace Measure { public delegate void DelegateTrigger(); public class HMeasureSYS : System.IDispo ...
- PHP命令行参数
原文地址:http://php.swoole.com/wiki/PHP%E5%91%BD%E4%BB%A4%E8%A1%8C%E5%8F%82%E6%95%B0 PHP命令行参数 目录 [隐藏] ...
- C/C++ 面试-内存对齐 即不同数据类型存储空间
下面列举了Dev-C++下基本类型所占位数和取值范围: 基本型 所占位数 取值范围 输入符举例 ...
- html5+springboot+websocket的简单实现
环境 window7,IntelliJ IDEA 2019.2 x64 背景:利用IntelliJ来搭建springboot框架,之后来实现websocket的功能.websocket只是实现了画面上 ...
- PHP设计模式 - 建造者模式
建造者模式主要在于创建一些复杂的对象.将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示的设计模式; 结构图: <?php /** * * 产品本身 */ class Pro ...
- Charles手机代理设置
Charles工具 手机 方法/步骤 1.打开Charles 点击Proxy,选择proxy settings,输入端口8888 打开电脑,在cmd中输入ipconfig,查看本地 ...
- C 风格字符串、string 类要点总结
1. C风格字符串 1.1 其它 头文件<cstring> 特殊性质:C风格字符串以空字符\0结尾 1.2 读取一行的区别 1.2.1 cin.getline(array1,n,char) ...
- centos7安装php7.3
安装php7.3 CentOS/RHEL 7.x: yum install epel-release yum install http://rpms.remirepo.net/enterprise/r ...