[JVM系列]别再说不知道volatile了
Posted Viola_tt
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[JVM系列]别再说不知道volatile了相关的知识,希望对你有一定的参考价值。
文章目录
闲聊
做程序员久了,如果你还是写那些crud,那真的应该好好的想一下了。觉得自己如果不了解点别的东西都对不起自己这些年的工作经验,之后出去不是什么10年的工作经验而是一个经验用了10年,不论你出于什么原因学习了jvm,都认为迈出这一步很棒,jvm的介绍我希望用自己的理解表达出来,如果有什么地方说的不好,欢迎大家指正,没准还能认识一下~
一、volatile
今天小编跟大家分享一下volatile,主要从她的定义,特性,原理,使用上进行分析,希望尽可能以小编认为通俗的方式说出来,当然如果有不对的地方,大家踊跃拍砖~
二、地(ding)位(yi)
volatile是JVM提供的轻量级同步机制,synchronized也是JVM提供的同步机制,这里为啥说volatile是轻量级呢,正是和synchronized对比得来的,synchronized是重量级的同步机制。volatile不能保证原子性,sync可以保证,至于什么是原子性,就不用我多说了吧。
三、特性
现在要划重点了,赶紧拿出你的小本本出来,volatile具有的特性是1. 可见性;2禁止指令重排,这个东西就算你不理解,那你也得知道~
3.1、可见性
可见性说的是,在高并发的情况下,多个线程同时访问一个用volatile修饰的变量,当其中一个线程修改了这个变量的值,其他线程可以立即看到变量的最新值。如果这句话不明白,我们看用代码来说话!
/**
* 验证volatile的特性
* Created by Viola on 2019/7/11.
*/
public class MyData
int number=0;
public void addT060()
this.number=60;
public void addTo60()
this.number=60;
class VolatileDemo
public static void main(String[] args)
//测试可见性
seeOkByVolatile();
//volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改
public static void seeOkByVolatile()
MyData myData=new MyData();
new Thread(()->
System.out.println(Thread.currentThread().getName()+"\\t come in ");
//暂停一会线程
try
TimeUnit.SECONDS.sleep(1);
catch (InterruptedException e)
e.printStackTrace();
;
myData.addTo60();
System.out.println(Thread.currentThread().getName()+"\\t update number value->"+myData.number);
,"childThread:").start();
//第2个线程时main线程
while (myData.number==0)
System.out.println(Thread.currentThread().getName()+"\\t mission is over,main get number value is :"+myData.number);
大家可以猜一下这段代码的运行完,最后一行打印的myData.number是啥,嘻嘻我怎么也开始出题了:
A、0;
B、60;
C、以上都不是;
如果将MyData 中的 int number=0;改为volatile int number=0;结果又是A、B、C中的哪一个呢?答案先不揭晓了,大家可以先思考一下,如果想要确定答案或是好奇答案,请留言,小编看到会回复~
上面是表层的,入门级别,也是不够的,人家都不怎么问你啥是可见性了,人家会问你的是volatile可见性是怎么保证的,他的原理是什么?没事别慌,下面原理会说~
3.2、禁止指令重排
我们写的代码顺序并不是真正执行时候的顺序,这个有疑问的举手~编译成.class的二进制文件,在解释执行时,cpu指令会做一些优化,使得顺序会发生改变。禁止指令重排说的是,我们用volatile修饰的这行代码的前后会分别生成一道不可逾越的屏障,volatile修饰的这样代码的前面的代码不会插进来,之后的代码也不会插进来,当然这行代码也不会跑出去。因此禁止指令重排,保证了代吗执行的有序性。
3.3、能够保证原子性吗?
NO,不行!经典的例子 i++;i++分三步 1,读取i,2.加1操作,3结果赋值给i。别多说了,show me the code~
/**
* 验证volatile的特性
* Created by Viola on 2019/7/11.
*/
public class MyData
volatile int number=0;
//i++,当前是volatile修饰
public void addPlusPlus()
number++;
class VolatileDemo
public static void main(String[] args)
//测试原子性
notAutomic();
//不能保证原子性
public static void notAutomic()
MyData myData=new MyData();
for (int i = 0; i < 10; i++)
new Thread(()->
for (int i1 = 0; i1 < 1000; i1++)
myData.addPlusPlus();
,String.valueOf(i)).start();
//等待上面的10个线程全部执行完成
while(Thread.activeCount()>2)
Thread.yield();
System.out.println(Thread.currentThread().getName()+"\\t finished , number value is \\t"+myData.number);
这段代码的运行结果我们发现,不是9000多就是8000多,循环了1万次,调用了1万次addPlusPlus(),但是结果没有到1万,但是最后的原子性能不能保证依然不用我多说了~
四、原理
划重点1 JMM
JMM(Java 内存模型 java memory model简称JMM)本身是一种抽象的概念,描述了一组规范,规定了程序中各个变量(实例字段,静态字段和构成数组对象的元素)的访问方式。
JMM关于同步的规定:
1. 线程解锁前,必须要把共享变量的值刷回到主内存中
2. 线程加锁前,必须读取主内存的最新值刷到自己的工作内存
3. 上面说的加锁和解锁是同一把锁
由于JVM运行程序的实体是线程,每个线程创建时JVM都会为其创建一个工作空间(栈空间),工作空间是每个线程私有的,而java内存模型规定所有变量都存储在主内存中,主内存中的变量是线程共享的,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先将变量从主内存拷贝到工作内存,对变量进行操作,操作完成后写回到主内存中,不能直接操作主内存中的变量,线程间的通信也通过主内存来完成,其简要访问过程如下:
划重点2 CPU指令重排
计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排:源代码–>编译器优化的重排–>指令并行的重排–>内存系统的重排–>最终执行的指令。如果两个操作之间的关系不存在下面所列举的先行发生(happen-before)情况,便会出现指令重排:
- 程序次序规则:程序执行时按照控制流顺序,要考虑分支和循环。
- 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作,这里需要强调2点:第一:同一个锁;第二:后面是时间上的先后。为什么这样呢?前一个线程unlock时可以将最新的变量刷回主内存,后面线程在lock时便会拿到最新的值。
- volatile变量规则:对一个volatile变量的写操作优先于后面对这个变量的读操作,原理同2,也是要刷回主内存。
- 线程启动规则:Thread对象的start()方法先行发生于次线程的每一个动作。
- 线程终止规则:线程中的所有操作先行发生于对此线程的终止检测。
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。(我对这句话实在是不理解,求指教)
- 对象终结规则:一个对象的初始化完成先行发生于它的finalize()方法的开始。
- 传递性:A操作先行发生于B,B先行与C,那么A先行与C。
将上面的总结一下可以得出:1.单线程里能够确保程序最终执行结果和代码顺序执行的结果一致。2、处理器在进行重排序时必须要考虑指令之间的数据依赖性。
四、使(dan)用(li)
哪里用我们可以用到volatile呢?其实我们常见的单例,保证线程安全的单例,便是用到了volatile,话不多说了,代码如下:
/**
* 懒汉模式,单例对象--synchronized
* 双层同步锁,+volatile 禁止指令重排序是线程安全的
* 单例实例在第一次使用时创建
* Created by Viola on 2019/5/8.
*/
@ThreadSafe
public class SingleTonExample5
//私有构造函数
private SingleTonExample5()
// 1、memory = allocate() 分配对象的内存空间
// 2、ctorInstance() 初始化对象
// 3、instance = memory 设置instance指向刚分配的内存
// JVM和cpu优化,发生了指令重排
// 1、memory = allocate() 分配对象的内存空间
// 3、instance = memory 设置instance指向刚分配的内存
// 2、ctorInstance() 初始化对象
//私有的单例对象
private volatile static SingleTonExample5 instance=null;
//共有的创建对象的方法
public static SingleTonExample5 getInstance()
if (instance==null) //A-1
synchronized (SingleTonExample.class)//双层检测机制
if (instance==null)
instance=new SingleTonExample5(); //A-3
return instance;
懒汉模式的线程安全的单例,对A-3这行做了详细的说明,如果不用volatile修饰,这时有的线程会拿到一个没有初始化完成的对象而出现错误。
五、结语
希望您能从小编的文章中得到一些感受或者启发,那将是我最大的荣幸,感谢您宝贵的阅读时间。
以上是关于[JVM系列]别再说不知道volatile了的主要内容,如果未能解决你的问题,请参考以下文章