神奇的volatile
什么是volatile?
打开google,百度一下,你就知道~
java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁更加方便。如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。
说的通俗一点,volatile能帮助我们实现一个重要的特性:保证在多线程处理下线程对资源修改的及时感知;说到volatile就一定会提及JMM内存模型和多线程任务并发场景。
线程中共享的内存,其实并不是全部作为主内存使用的,每个线程对应有自己的堆栈内存,那么我们平时写一个thread,多线程测试会发现,一个共享变量在经过修改后还是会被其他线程所感知,这是为什么呢?
这里就要说到cpu相关的硬件结构了,最早如果单核cpu单线程,完全不需要考虑这种情况,反正从头到尾都是你自己玩,想玩到天荒地老也不会有人管你。
现在的服务器标配多核多线程,如果我有一个并发任务去处理,这时候我们并没有办法控制哪个任务会在哪个cpu切片上,具体的cpu如何分配线程,我们以后再说 !
我们可以看一段代码,这种写法在并发下会有问题:
i = i + 1;i ++
为什么说这种方式会有并发问题呢,我们知道并发的三个特点:
- 原子性:指一个操作是不可中断的,只能一次性执行完,一个操作一旦开始就不 会被其他线程影响
- 可见性:如果一个线程修改了资源,其他线程不可见,我们就会说它是存在线程安全问题
- 有序性:多线程无法保证顺序,在没有相关依赖关系时,会有指令重排的问题
在jvm中,Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围,就需要使用同步块或锁,顾名思义,上面的代码不是原子操作,它其实是将i的值取出来、再操作自增或者增加,再写入,这就是一个比较常见的并发问题:两个线程同时做i++
操作,第一个线程先获取到了i的值,给i加1,这时候再写入,但是主内存还并没有将变量的值同步;这时第二个线程来获取i的值(这时候其实获取的是i的脏数据,并没有做完i++动作),它又将i加1后写入;
针对这种情况,jvm帮我们提供了AtomicReference可以完成自增或者自增数之类的操作,它的底层是由指令实现的,可以避免这种情况,针对AtomicReference我们以后再详细聊聊。
有了volatile,它能帮助我们做什么?
有了volatile,它最大的特点是,将一个共享变量及时刷新到共享内存中去,使其他线程也可以立即感知到它的变化。
其实在jvm,字节码文件中,jvm对加了关键字volatile的变量,在变量修改后立即同步主内存并刷新到共享内存中,其他线程可以立即感知
实例
boolean isRun = false;
while(!isRun){
//doThings
}
isRun = true;
上面这种自旋唤醒执行的条件,只有当isRun为true时才会执行,上面两步可以分为两个线程执行,如果第二个线程执行了isRun = true的条件,但是主内存并没有同步到共享内存中,线程1是无法感知的,它还会去执行直到主内存同步完成。
同时,volatile还能够在一定情况下保证有序性:
volatile可以禁止指令重排,指令重排的意思就是,jvm在优化执行过程时,对没有依赖的相关代码,并不会完全按照代码的顺序执行:
实例
int i = 0;
boolean flag = false;
i = 1; //语句1
flag = true; //语句2
语句1和语句2,我们不能保证肯定是语句1先执行语句2后执行,这就是指令重排的意思,flag和i并没有相互依赖的东西,jvm认为它们重排并不会影响最终结果(思路有点像存储读写分离的保证最终一致性)
摘自//blog.csdn.net/qq_42569136/article/details/123219118
private static ObjInstance instance;
//线程1
if(instance!=null){
instance.method();
}
//线程2
if(instance==null){
synchronized (ObjInstance.class){
if(instance==null){
instance = new ObjInstance();
}
}
}
这种情况,就是指令重排后会导致出现执行顺序的问题
我们应该在什么时候使用volatile?
synchronized关键字是防止多个线程同时执行一段代码,那么就会很影响程序执行效率,而volatile关键字在某些情况下性能要优于synchronized,但是要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性
本文来自博客园,作者:青柠_fisher,转载请注明原文链接:https://www.cnblogs.com/oldEleven/p/16708370.html