java多线程之volatile讲解
?? 最近一直在看多线程的一些知识,看了一些书和一些博客,收获还是挺多的,最近看了《java并发编程的艺术》这本书感觉收获很大也推荐给各位,同时也结合以前看的博客就好好的总结一下自己所学的东西吧,有不足的地方欢迎各位指正,这篇文章主要是讲volatile关键字的知识。
volatile的特性
- 可见性:volatile在多线程中能够保证共享变量的“可见性”,简单的说就是当一个线程修改了volatile变量的时候,java线程内存模型能够确保所有的线程看到的这个变量的值是一致的。
- 防止指令重排序
java内存模型
- 在学习volatile的知识之前我们先来简单了解下java内存模型(JMM)引用一张网上很经典的表示java内存模型的图
- 大概解释下这个图的意思
- 在多个线程运行的时候每一个线程(Thread)都有一个属于自己的内存空间
- 多个线程共同使用一个主存
- 每个线程在对数据进行修改之前都会先在主存里面获取相关数据,然后在自己的工作内存里面对数据进行操作。
可见性
关键的地方就来了,因为每个线程然都是在自己的内存里进行操作,然而每个线程的工作内存之间都是相互不可见的,所以对共享变量的修改并不会马上被其他线程看到,所以就会造成多个线程操作同一个数据但是最后结果并不是我们期望的结果。当线程1去首先从主存中加载一个volatile变量到自己的工作内,然后对这个volatile变量进行写操作,写入操作结束之后,volatile变量的最新值会立马刷新到主内存,同时其他线程中的这个volatile变量会立马失效,会被强迫从主内存中重新读取volatile变量的最新值,这就是volatile变量的可见性实现的过程。同时这也可以看做是一个线程和其他线程通信的一个过程。
防止指令重排序
简单解释下指令重排序,重排序指的是编译器和处理器为了优化程序的性能会对指令序列进行重新排序的一种手段。在单线程的程序里,指令的重排序会保证执行结果的正确性,但是在多线程中指令的重排序对程序的执行结果的正确性就得不到保障(指令重排序的一些规则各位可以去查阅一下,这里不赘述)。volatile变量防止指令重排序请先看下面的内存屏障介绍。(其实synchronized也对指令的重排序有一些限制,在后面我会写一篇讲解synchronized的博文会详细讲解一下,欢迎各位阅读)
内存屏障
JMM把内存屏障分为四类(摘自Java并发编程艺术)
| 屏障类型 | 指令示例 | 说明 |
| ----------------- |:-------------:| -----:|
| LoadLoad Barriers | Loadl; LoadLoad; Load2 | 确保Loadl数据的装载先于Load2及所有后续装载指令 |
| StoreStore Barriers | Storel; StoreStore; Store2 | 确保Store1数据对其他处理器可见(刷新到内存)先于Store2及所有后续存储指令的存储|
|LoadStore Barriers | Loadl; LoadStore; Store2 |确保Loadl数据装载先于Store2及所有后续的存储指令刷新到内存|
|StoreLoad Barriers |Storel; StoreLoad; Load2 |确保Storel数据对其他处理器变得可见(指刷新到内存)先于Load2及所有后续装载指令的装载。StoreLoad Barriers会使该屏障之前的所有内存访问指令(存储和装载指令)完成之后,才执行该屏障之后的内存访问指令|
- volatile变量基于保守策略的JMM内存屏障插入策略
- 在每个volatile写操作的前面插入一个StoreStore屏障。
- 在每个volatile写操作的后面插入一个StoreLoad屏障。
- 在每个volatile读操作的后面插入一个LoadLoad屏障。
- 在每个volatile读操作的后面插入一个LoadStore屏障。
看完这个相信各位对volatile防止指令重排序就有一个比较清楚的认识了,解释一下上面的四条策略,
- 在volatile变量的写操作前面的其他写操作会在volatile变量写前面执行(提前刷新到主存,对其他线程可见)
- volatile变量的写会比后面的其他读写操作先进行
- volatile变量读操作前面的读操作会在volatile变量读操作以前进行
- volatile变量读操作后面的其他写操作会在volatile变量读操作以后进行
volatile的应用
很多博客中基本上都说了volatile变量一般运用于不依附当前值的操作,比如自增,我的理解是这样的,如果volatile变量进行依附当前操作的值的运算,那么就会涉及到读volatile变量和写volatile变量这两个操作,volatile变量的读操作(从主内存读取到线程的工作内存)和写操作(将变量写到主内存中去)时,这两个组合起来的操作就是一个非原子性的操作,所以这种情况下使用volatile关键字就不合适,对于基本变量的一些非原子性操作(如自增)可以考虑使用java.util.concurrent.atomic包下的一些类,或者使用锁来进行。volatile变量一般用作比如一个标志变量这种单个读写的操作。
附上个人觉得volatile变量应用的一个讲得比较好的博客(https://www.ibm.com/developerworks/cn/java/j-jtp06197.html)
同时各位也可以去看一下另外一篇讲解volatile的博文,同样比较棒(http://kwsir.cn/2017/10/12/Java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B-volatile%E5%8F%AF%E8%A7%81%E6%80%A7%E7%9A%84%E4%BB%8B%E7%BB%8D/)。
写在最后
鉴于本人水平有限,所以如果文章中有不对的地方,十分欢迎各位在评论留言指点,或者发送到本人的qq邮箱[email protected]通知一下本人。谢谢大家。
原文地址:https://www.cnblogs.com/NewHongjay/p/9047986.html