摘要:多线程是一个庞大的知识体系,这里对其中的进行一个总结,理清他的来龙去脉。替换重量级锁在中又称为重量级锁,能够保重的几大特性一致性,原子性,可见性。
Java多线程是一个庞大的知识体系,这里对其中的volatile进行一个总结,理清他的来龙去脉。
CPU缓存要搞懂volatile,首先得了解CPU在运行过程中的存储是如何处理的,其结构如图
CPU会把一些经常使用的数据缓存在cache中,避免每次都去访问较慢的memory。在单线程环境下,如果一个变量的修改都在cache中,自然不会有什么问题,可是在多线程环境中就可能是下面这个图的示意图(单核另当别论)
CPU1 修改了一个变量a存入cache1,但是CPU2 在cache2中看到的a任然是之前的a,所以造成CPU1修改失效,我们来看看示例代码:
import java.util.concurrent.TimeUnit; public class Counter { private static boolean stop ; //private static volatile boolean stop ; public static void main(String[] args) throws Exception { Thread t = new Thread(new Runnable() { @Override public void run() { int i = 0; while (!stop) { i++; } } } ); t.start(); TimeUnit.MILLISECONDS .sleep(5); stop = true; } }
在我的4核笔记本上运行结果:
就一直运行着,没有停止(需要手工停止),这说明在主线程中修改的stop变量后,线程t没有读取到最新的stop的值,还一直是false。
volatile原理volatile的原理就是,如果CPU1修改了一个变量a,不仅要修改自身的cache,还要同步到memory中去,并且使CPU2的cache中的变量a失效,如果CPU2要读取a,那么就必须到memory中去读取,这样就保证了不同的线程之间对于a的可见性,亦即,无论哪个线程,随时都能获得变量a最新的最新值。
我们来看看示例代码:
import java.util.concurrent.TimeUnit; public class Counter { //private static boolean stop ; private static volatile boolean stop ; public static void main(String[] args) throws Exception { Thread t = new Thread(new Runnable() { @Override public void run() { int i = 0; while (!stop) { i++; } } } ); t.start(); TimeUnit.MILLISECONDS .sleep(5); stop = true; } }
在我的4核笔记本上运行结果:
很快程序就结束了,说明线程t读到了经主线程修改后的stop变量,然后就停止了。
(例子源于《effective Java》)
volatile使用场景 状态标志就像上面的代码里,把简单地volatile变量作为状态标志,来达成线程之间通讯的目的,省去了用synchronized还要wait,notify或者interrupt的编码麻烦。
替换重量级锁在Java中synchronized 又称为重量级锁,能够保重JMM的几大特性:一致性,原子性,可见性。但是由于使用了锁操作,在一定程度上会有更高的性能消耗(锁的线程互斥性亦即资源消耗)。而volatile能提供可见性,原子性(单个变量操作,不是a++这种符合操作),所以在读写上,可以用volatile来替换synchronized的读操作,而写操作仍然有synchronized实现,能取得更好的性能。
import java.util.ArrayList; import java.util.List; public class Counter1 { private class Count11 { private int value; public synchronized int getValue() { return value; } public synchronized int increment() { return value++; } } // private class Count11 { // private volatile int value=0; // int getValue() { return value; } // synchronized int increment() { return value++; } // } public static void main(String[] args) throws Exception { Counter1.Count11 count11 = new Counter1().new Count11(); ListthreadArrayList = new ArrayList<>(); final int[] a = {0}; Long allTime = 0l; long startTime = System.currentTimeMillis(); for (int i = 0; i < 4; i++) { Thread t = new Thread(() -> { int b = 0; for (int j = 0; j < 10000; j++) { count11.increment(); a[0] = count11.getValue(); } for (int j = 0; j < 10000; j++) { b++; a[0] = count11.getValue(); } }); t.start(); threadArrayList.add(t); } for (Thread t : threadArrayList) { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } } long endTime = System.currentTimeMillis(); allTime = ((endTime - startTime)); System.out.println("result: " + a[0] + ", average time: " + (allTime) + "ms"); } }
volatile优化结果:
result: 40000, average time: 124ms result: 40000, average time: 133ms result: 40000, average time: 141ms result: 40000, average time: 112ms result: 40000, average time: 123ms result: 40000, average time: 143ms result: 40000, average time: 120ms result: 40000, average time: 120ms
未优化结果:
result: 40000, average time: 144ms result: 40000, average time: 150ms result: 40000, average time: 149ms result: 40000, average time: 165ms result: 40000, average time: 134ms result: 40000, average time: 132ms result: 40000, average time: 157ms result: 40000, average time: 138ms result: 40000, average time: 158ms
可见使用volatile过后效果的确优于只使用synchronized的性能,不过试验中发现有个阈值,如果读取修改次数较小,比如1000以内,只使用synchronized效果略好,存取次数变大以后 volatile的优势才慢慢体现出来(次数达到10000的话,差距就在60ms左右)。
待挖掘还有很多用法,在将来的学习中,不断总结与挖掘。
联想无论处于应用的哪一层,优化的思路都是可以相互借鉴的,比如我们做一个服务集群,如果每一个节点都要保存所有用户的session,就很难使得session同步,我们就可以借鉴volatile这种思路,在集群之上搞一个调度器,如果某一个节点修改了一个用户session,就报告给调度器,然后调度器通知其他所有节点修改该用户session。而一般情况下,数据的读写比都比较高,所以这样做就能到达一个很好的性能。
注意事项引用类型的volatile只在引用本身发生变化时具有可见性,其引用的对象的元素发生变化时不具有可见性
欢迎访问我的个人主页 mageek(mageek.cn)
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/67040.html
摘要:阅读本文约分钟上一次我们说到互斥代码的实现过程,如果有忘记或不清楚的可以去上篇看看。猫说多线程之内存可见性上篇今天我们了解下重排序。 阅读本文约3分钟 上一次我们说到synchronized互斥代码的实现过程,如果有忘记或不清楚的可以去上篇看看。【Java猫说】Java多线程之内存可见性(上篇) 今天我们了解下重排序。 其使代码书写的顺序与实现执行的顺序不同,指令重排序是编译器或处理...
时间:2017年07月09日星期日说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com教学源码:无学习源码:https://github.com/zccodere/s... 第一章:课程简介 1-1 课程简介 课程目标和学习内容 共享变量在线程间的可见性 synchronized实现可见性 volatile实现可见性 指令重排序 as-if-seria...
摘要:前半句是指线程内表现为串行的语义,后半句是指指令重排序现象和工作内存和主内存同步延迟现象。关于内存模型的讲解请参考死磕同步系列之。目前国内市面上的关于内存屏障的讲解基本不会超过这三篇文章,包括相关书籍中的介绍。问题 (1)volatile是如何保证可见性的? (2)volatile是如何禁止重排序的? (3)volatile的实现原理? (4)volatile的缺陷? 简介 volatile...
摘要:前半句是指线程内表现为串行的语义,后半句是指指令重排序现象和工作内存和主内存同步延迟现象。关于内存模型的讲解请参考死磕同步系列之。目前国内市面上的关于内存屏障的讲解基本不会超过这三篇文章,包括相关书籍中的介绍。问题 (1)volatile是如何保证可见性的? (2)volatile是如何禁止重排序的? (3)volatile的实现原理? (4)volatile的缺陷? 简介 volatile...
摘要:前半句是指线程内表现为串行的语义,后半句是指指令重排序现象和工作内存和主内存同步延迟现象。关于内存模型的讲解请参考死磕同步系列之。目前国内市面上的关于内存屏障的讲解基本不会超过这三篇文章,包括相关书籍中的介绍。问题 (1)volatile是如何保证可见性的? (2)volatile是如何禁止重排序的? (3)volatile的实现原理? (4)volatile的缺陷? 简介 volatile...
阅读 3678·2021-11-24 10:46
阅读 1679·2021-11-15 11:38
阅读 3719·2021-11-15 11:37
阅读 3414·2021-10-27 14:19
阅读 1919·2021-09-03 10:36
阅读 1971·2021-08-16 11:02
阅读 2981·2019-08-30 15:55
阅读 2233·2019-08-30 15:44