彻底理解volatile
volatile原理
在Java并发编程中synchronized和volatile扮演者重要的角色,volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的可见性和程序执行的有序性。如果volatile使用恰当的话,它比** **synchronized的使用和执行成本更低,因为它不会引起线程上下文切换和调度。 我们了解一下计算机,例如在我们工作当中大多数都是多核,由于CPU和物理主存速度不一致问题,为了解决CPU读取内存指令和数据效率问题,诞生了CPU高速缓存。
private volatile instance = new Singleton();
在生成汇编代码时会在volatile修饰的共享变量进行写操作的时候会多出Lock前缀的指令 ,Lock前缀指令在多核处理器下会发生两件事。 将当前处理器缓存行的数据写回系统内存; 这个写回内存的操作会使得其他CPU里缓存了该内存地址的数据无效
为了提高处理器速度,首先处理器不直接和内存进行通信,而是先将系统内存的数据读到高速缓存(L1,L2,L3)后再进行操作,但是操作何时会写到内存,如果对声明volatile的变量进行写操作,JVM 就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写到系统内存。但是就算写到内存,如果其他处理器存在的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址别修改,就会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。 因此,我们得出一下结论: Lock前缀的指令会引起处理器缓存写回内存; 一个处理器的缓存回写到内存会导致其他处理器的缓存失效; 当处理器发现本地缓存失效后,就会从内存中重读该变量数据,即可以获取当前最新值。
这样针对volatile变量通过这样的机制就使得每个线程都能获得该变量的最新值,即可见性。 可见性
那么下面我们通过代码证明volatile如何保证可见性? 保证不同线程对这个变量进行操作时的可见性,即变量一旦改变所有线程立即可以看到 /** * 保证可见性 */ public class ResourceData { // voliate 关键字能保证变量的可见性,多个线程修改同一个变量时,一个线程修改完,另一个线程获取到的是修改之后的值。 private volatile int number = 0; public void add() { this.number = 10; } public static void main(String[] args) { ResourceData resourceData = new ResourceData(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + " come in "); try { TimeUnit.SECONDS.sleep(3); resourceData.add(); System.out.println(Thread.currentThread().getName() + " update number value " + resourceData.number); } catch (InterruptedException e) { e.printStackTrace(); } }, "AA").start(); // 第二个线程是main while (resourceData.number==0) { } System.out.println(Thread.currentThread().getName()+" mission is over ,main get number value :" +resourceData.number); } } AA come in AA update number value 10
如果共享变量不加volatile ,没有可见性,程序无法停止,加了volatile保证可见性,程序可以停止。
原理解释: 没有添加volatile关键字,线程A对共享变量改变了以后将number修改为10,主线程(线程B)访问number的值还是0,这就是不可见。 添加volatile之后,线程A对共享数据进行了改变以后,那么main线程再次访问,number的值就是改变之后的number=10 原子性
不保证原子性,代码实现: /** * 不保证原子性 */ public class ResourceData2 { // volatile 关键字不能保证变量的原子性,当多个线程修改一个变量的时候可能回出现写丢失的情况。 // 如何保证数据的原子性呢 private volatile int number = 0; // AtomicInteger 包装类可以保证变量的原子性 AtomicInteger atomicInteger = new AtomicInteger(); public void addAtomic() { atomicInteger.getAndIncrement(); } public void add() { number++; } public static void main(String[] args) { ResourceData2 resourceData = new ResourceData2(); for (int i = 0; i <20 ; i++) { new Thread(()-> { for (int j = 0; j < 1000; j++) { resourceData.add(); resourceData.addAtomic(); } },String.valueOf(i)).start(); } // 等待上面的线程全部计算完,再通过main线程获取最终的结果 while (Thread.activeCount()>2) { // 指main 和Gc Thread.yield(); } System.out.println(Thread.currentThread().getName()+" finally number value :" +resourceData.number+" finally atomicInteger value :"+resourceData.atomicInteger); } } main finally number value :19778 finally atomicInteger value :20000
预期结果是:20000,但是实际结果是:19778,那么是什么原因导致的呢? 首先对于一读一写操作,不会有数据问题 ,因为假设主内存的共享变量number=1,需要对主内存的number++处理,对于两个线程T1、T2如果是一读一写的操作是不会有数据丢失的情况,某一时刻,t1抢到CPU的执行权,将共享数据读回T1的工作内存,进行number++的操作,这个时候number=2,将2从工作内存写回到主内存中。写回后马上通知T2线程,将number=2读到T2的工作线程,所以不会造成数据丢失问题。 对于两个写,会出现数据问题,假设主内存的共享变量number=0,需要对主内存进行10次的number++处理,最终的结果就是10,对于两个线程T1、T2如果是两个写的操作会造成数据丢失的情况,T1和T2将主内存的共享数据读取到各自的工作内存去,某一时刻,T1线程抢到CPU的执行权,进行number++的处理,将工作内存中的number=1写回到主内存中,就在这一刻,T2也抢到CPU执行权,进行number++的处理,这个时候number++后的结果也等于1,T1将number=1写回到主内存中去,并通知T2线程,将主内存中的number=1读到T2的工作内存中去,这个时候对于T2,它之前也进行了一次number++的操作将会无效,回重新进行一次number++的操作。这也数据也就写丢了一次,那么10次number++后的结果也就不会等于10。 禁止指令重排
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序(不存在数据依赖关系,可以重排序;存在数据依赖关系,禁止重排序) 重排序的分类和执行流程 编译器优化的重排序:编译器在不改变单线程串行语义的前提下,可以重新调整指令的执行顺序 指令级并行的重排序:处理器使用指令级并行技术来将多条指令重叠执行,若不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序 内存系统的重排序:由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是乱序执行
数据依赖性:若两个操作访问同一变量,且这两个操作中有一个为写操作,此时两操作间就存在数据依赖性(存在数据依赖关系,会禁止重排序,因为会导致程序运行结果不同), 如果不存在依赖关系,可以重新排序。
如果本文对你有帮助的话,欢迎点赞,非常感谢,欢迎关注公众号: 阿福聊编程
老徐谈茶第293期老徐带你了解名山名寨系列之同庆河今天继续和大家聊名山名寨系列同庆河。知道易武瑶家三寨的茶友估计并不多,但提到弯弓刮风寨同庆河这三个茶大家应该都挺熟的吧!它们三个就是瑶家三寨著名的山头,在普洱茶界中应该算是如雷贯耳
坐火车翻越喜马拉雅山脉,魔幻通往现实在喜马拉雅山脉面前,地球上所有山峦都会黯然无光。绵延2450公里的一众山峰皆高耸入云,平均海拔突破7000米,其间矗立着世界最高峰,令人望而却步。古人常常将一些难以逾越的山脉与河流
这些遗址保护公园,见证了北京的历史变迁北京晚报五色土作者景长顺故都北京,现在有大大小小的公园1090个,称得上规模宏大,类别齐全,特色鲜明,底蕴深厚。这为数众多的公园里,有不少遗址保护公园,分别和古代一段时期的王朝有着
自驾河北内蒙新疆(41)9月1号,不知不觉已经进入九月份了。今天按计划去正镶白旗。我们导航走国道。207国道路况不错,也很通畅。可是走到一半的时候,突然被拦住,说前面修路,必须上高速。没办法,走高速吧。还
在鼋头渚的岸边这是四月的清晨,雾蒙蒙,今晨的太湖特别宁静,潮水卷起细细的波浪向岸边涌来。湖面上有一层薄薄的雾霭,远处的景色被雾笼罩,分不清水与天。年轻的我与妻子坐在一块硕大的巨石上面,听着湖水轻
中国蜜月旅行产业政府战略管理与区域发展战略研究咨询报告蜜月旅游产业消费需求及发展趋势的分析与研究据悉,蜜月游基本成为新婚人群的刚性需求。随着90后消费者对婚纱照和婚礼的个性化追求,海外婚纱照和婚礼逐渐流行起来。传统跟团游是蜜月客户的最
藏在香山公园里的老牌餐厅藏在香山公园内的一家国营餐厅,开了有30多年了。可以在园内悠然自得的享受美食,欣赏美景旁边就是香山公园的眼镜湖。餐厅有露天位和室内位,提供早餐(糖油饼很火)。糖醋里脊必点菜,传统手
官厅水库附近观澜小洱海项目怎么样?官厅水库附近观澜小洱海项目怎么样?今天继续和大家讨论一下吧,有不一样见解的欢迎评论沟通哦在我心里理解中的家,是一种远离繁华且静谧于岁月中的安好,每天清晨睡醒以后都可以伴随清风徐徐来
9月最佳旅游地推荐迎接层林尽染五彩斑斓的秋光夏日的余热已在悄然散去,秋色将至,国内几地已迎来2022年的初雪,这抹凉爽羡煞了还挣扎在30度高温的南方地区。随着秋意渐浓,气温逐步走低,赶紧把秋游计划安排起来,以免错过这层林尽染
京城夜生活精彩夜游北京地图来袭!开启你的初秋夜游之旅说起京城夜生活您会想到哪?今天,迷妹就和大家分享几个夜游好去处,一起来感受下北京夜晚的魅力!北京欢乐谷如果说白天的欢乐谷是惊心动魄,那么夜晚的欢乐谷就是流连忘返!北京欢乐谷,可谓是
地球位置可能被监视?太阳系暴露在附近上千个恒星系统的视野中头条创作挑战赛1。截至目前,天文学家已通过望远镜观测到4000多颗系外行星,并在这其中找到了许多可能移居的行星世界。2。由此可以看出,发现外星生命或许只是时间问题。说到这里,可能有