面试(三)---volatile
一、前言
最近去成都玩了一圈,整体感觉还不错,辞职以后工作找的也很顺利,随着年龄增加自己也考虑定居和个人长期发展的问题,反正乱七八糟的事,总之需要好好屡屡思路,不能那么着急下定论,当然我对下份工作也是有所期望的,不扯了开始我们今天主题吧。
二、Java的内存模型
Java内存模型规定所有的变量都存在主内存当中,每条线程都有自己的工作内存,线程的工作内存保存了被该线程使用的变量的主内存副本拷贝,线程对变量的所有操作都在内存中进行,而不能直接读写主内存中的变量。不同的线程之间无法直接访问对工作内存中的变量,线程之间变量值的传递均需要通过主内存来完成。----来自深入理解Java虚拟机
这里注意下,Java的内存模型(JMM)和Java的内存区域的差别,不要混淆这两者之间的概念,JMM主要是围绕在程序中各个变量在共享数据区域和私有数据区域的访问方式。JMM与Java内存区域唯一相似点,都存在共享数据区域和私有数据区域,在JMM中主内存属于共享数据区域,从某个程度上讲应该包括了堆和方法区,而工作内存数据线程私有数据区域,从某个程度上讲则应该包括程序计数器、虚拟机栈以及本地方法栈。或许在某些地方,我们可能会看见主内存被描述为堆内存,工作内存被称为线程栈,实际上他们表达的都是同一个含义。
接下来我们来看下主内存和工作内存之间的交互问题:
1.lock操作,锁定主内存变量,标识为当前线程独占状态;
2.read操作,将锁定的主内存变量读取到工作内存当中;
3.load操作,将read操作的主线程变量放入到工作变量的副本当中;
4.use操作,将工作内存的变量传递给执行引擎,当虚拟机调用到该变量的时候执行该变量;
5.assign操作,当虚拟机对工作内存的变量的值进行赋值操作的时候,将值赋值给工作内存的变量;
6.store操作,将工作内存的变量传递给主内存变量;
7.write操作,将store操作的工作变量写入工作变量;
8.unlock操作,将锁定的主内存中的变量锁释放,等待其他线程锁定;
明白这些我们再来探究下JMM如何处理原子性、可见性和有序性的问题:
1.原子性
Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。
2.可见性
由于JMM结构原因,当多线程访问的时候不能及时将变量的值更新到主内存当中,这个时候就能出现数据不一致的问题,Java提供了volatile关键字来保证可见性,另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。
3.有序性
在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
JMM中有序性可以通过volatile实现,另外通过synchronized和Lock也能够保证有序性,这个和保证可见性的原理一样;另外Java语言有一个“先行发生(happens-before)”的原则,如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
下面就来具体介绍下happens-before原则(先行发生原则):
1.程序次序规则:一个线程内书写在前的代码先执行,写在后面的代码后执行;
2.锁定规则:一个unLock操作先行发生于lock操作;
3.volatile规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
4.线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
5.线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
6.线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
7.传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
8.对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始
三、volatile
上面聊了很多,接下来我们看下volatile,
volatile作用:
1.保证变量的可见性;
public class VolatileVisibility {
public static volatile int i =0; public static void increase(){
i++;
}
}
针对于可见性我们分析下上面的代码,i被volatile修饰的情况下,i的任何改变都会反应到其他线程当中,但是当多线程同时调用increase()的方法时,会出现线程安全的问题,i++不是原子操作,该操作先读后写,分2步完成,如果第二个线程在第一个线程读取旧值和写回新值期间读取i的域值,那么第二个线程就会与第一个线程一起看到同一个值,并执行相同值的加1操作,这也就造成了线程安全失败;因此对于increase方法必须使用synchronized修饰,以便保证线程安全。
接下来我们再看一个例子:
public class VolatileDemo { volatile boolean close; public void close(){
close=true;
} public void doWork(){
while (!close){
System.out.println("work...");
}
}
}
被volatile修饰的close字段,字段可以立即可见,保证当多个线程同时访问实例的时候,一个线程对close进行更新另外一个线程可立即获取到该字段变化以后的值;当写一个volatile变量时,JMM会把该线程对应的工作内存中的共享变量值刷新到主内存中,当读取一个volatile变量时,JMM会把该线程对应的工作内存置为无效,那么该线程将只能从主内存中重新读取共享变量。
对比下得出一个结论:volatile并不能保证原子性,只能保证可见性;
2.防止指令重排序;
明白这个要我们需要知道一个概念:内存屏障(Memory Barrier)是一个CPU指令,它的作用有两个,一是保证特定操作的执行顺序,二是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。Memory Barrier的另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。
接下来我们看下我们最常见的单例模式:
public class SingletonDemo {
private volatile static SingletonDemo instance;
private SingletonDemo(){
System.out.println("Singleton has loaded");
}
public static SingletonDemo getInstance(){
if(instance==null){
synchronized (SingletonDemo.class){
if(instance==null){
instance=new SingletonDemo();
}
}
}
return instance;
}
}
这里我们思考下没有volatile的时候出现的问题:当然在单线程的情况下是不会出现问题,多线程下面会出现问题,首先这里我们要声明下volatile在这个地方只是防止指令重排,可见性是由锁实现的,接下来我们在分析为什么多线程会出现问题?
正常情况下初始化一个对象的过程如下:
1.分配内存空间;
2.初始化对象;
3.将内存空间的地址赋值给对象的引用;
当发生指令重排的时候可能会发生如下状况:
1.分配内存空间;
2.将内存空间的地址赋值给对象的引用;
3.初始化对象;
这个时候我们就来考虑下多线程情况下可能发生的问题喽,如果A线程正好初始化到了第(2)步的时候, 正好有其它线程B来获取这个对象, 那线程B能不能看到这个由A初始化但是还没初始化完毕的对象呢?答案是可能会看到这个未完全初始化的对象, 因为这里初始化的是一个共享变量,这个时候就会照成2种情况:
1.如果读到的是null,反而没问题了,接下来会等待锁,然后再次判断时不为null,最后返回单例。
2.如果读到的不是null,那么坏了,按逻辑它就直接return instance了,这个instance还没执行构造参数,去做事情的话,很可能就崩溃了。
注意:这个问题不容易出现理解下就好,不要和我一样调试到怀疑人生。。。。。
四、总结
JMM就是一组规则,这组规则为了处理在并发编程可能出现的线程安全问题,并提供了内置的(happen-before原则)以及其外部可使用的同步手段(synchronized/volatile等),保证程序在执行多线程时候的原子性,可见性以及有序性。
volatile不能保证原子性;
欢迎加群:438836709
欢迎关注公众号:
下一篇:spring IOC源码解读
面试(三)---volatile的更多相关文章
- [Java面试三]JavaWeb基础知识总结.
1.web服务器与HTTP协议 Web服务器 l WEB,在英语中web即表示网页的意思,它用于表示Internet主机上供外界访问的资源. l Internet上供外界访问的Web资源分为: • 静 ...
- Unity3D 面试三 ABCDE
说说AB两次面试: “金三银四” 三月份末又面试过两家:共和新路2989弄1号1001这家找了我半天,哇好漂亮的办公大楼!问了保安才知道,这个地址是小区地址.另一家也是创业公司面试我的自称是在腾讯做过 ...
- 三 volatile关键字
一:内存模型: 大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入.由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问 ...
- 面试并发volatile关键字时,我们应该具备哪些谈资?
提前发现更多精彩内容,请访问 个人博客 提前发现更多精彩内容,请访问 个人博客 提前发现更多精彩内容,请访问 个人博客 写在前面 在 可见性有序性,Happens-before来搞定 文章中,happ ...
- django面试三
1.Django. Flask.Tornado框架的比较? Django: 对于django,大而全的框架它的内部组件比较多,内部提供:ORM.Admin.中间件.Form.ModelForm.Ses ...
- Java并发编程的艺术(三)——volatile
1. 并发编程的两个关键问题 并发是让多个线程同时执行,若线程之间是独立的,那并发实现起来很简单,各自执行各自的就行:但往往多条线程之间需要共享数据,此时在并发编程过程中就不可避免要考虑两个问题:通信 ...
- Android面试三之Service
Service是什么 Service(服务)是一个没有用户界面的在后台运行执行耗时操作的应用组件.其他应用组件能够启动Service,并且当用户切换到另外的应用场景,Service将持续在后台运行.另 ...
- java面试总躲不过的并发(二):volatile原理 + happens-before原则
一.happens-before原则 同一个线程中的,前面的操作 happens-before 后续的操作.(即单线程内按代码顺序执行.但是,在不影响在单线程环境执行结果的前提下,编译器和处理器可以进 ...
- Java多线程学习(三)volatile关键字
转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79680693 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...
随机推荐
- 菜鸟玩云计算之十二:KVM虚拟机更改大小
菜鸟玩云计算之十二:KVM虚拟机更改大小 参考: http://www.missionfamilybank.org/expanding-resizing-your-qcow2-virtual-mach ...
- 谈谈Ext JS的组件——布局的使用方法
概述 在Ext JS中,包含两类布局:组件类布局和容器类布局.由于有些组件是有不同的组件组合而成的,如字段就由标题和输入框构成,他们之间也是存在布局关系的,而这就需要组件类布局来处理组件内自己特有的布 ...
- (NO.00003)iOS游戏简单的机器人投射游戏成形记(一)
这是一个简单的机器人投射游戏,主要来熟悉物理引擎的一些东西.你可以把它认为是机器人投篮;尽管投出的是抛物线,但不是篮球而是子弹,速度也较快. 游戏玩法是玩家选择机器人,移动机器人手臂瞄准篮框,然后发射 ...
- H5学习之旅-H5列表(8)
列表的基本语法 ol:有序列表 ul:无序列表 li:列表项 dl:列表 dt:列表项 dd:列表描述 常用列表 1.无序列表:使用标签 ul,li 属性:disc(默认实心圆) circle (空心 ...
- Android实训案例(八)——单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局
Android实训案例(八)--单机五子棋游戏,自定义棋盘,线条,棋子,游戏逻辑,游戏状态存储,再来一局 阿法狗让围棋突然就被热议了,鸿洋大神也顺势出了篇五子棋单机游戏的视频,我看到了就像膜拜膜拜,就 ...
- 【翻译】Sencha Ext JS 5发布
原文:Announcing Sencha Ext JS 5 简介 我代表Sencha和整个Ext JS团队,很自豪的宣布,在今天,Sencha Ext JS 5发布了.Ext JS 5已经迈出了一大步 ...
- C#之结尾篇
在Top10语言中,C#是最优美的语言,没有之一,在Top10语言中,C#所可用的标准库及可获得其他库是最强大的之一,这个必须带上之一,因为有java在,在Top语言中,C#语言是性能最高的语言之一, ...
- 反对网抄,没有规则可以创建目标"install" 靠谱解答
在ubuntu下遇到这个问题,原因其实很简单,你不能用WINDWOS下的方法用图形方式打开,然后点了一下按扭"解压缩",生成了一个文件夹. 的确,这个文件夹看起来和正常的没有什么区 ...
- 【Qt编程】Qt版扫雷
学习要学会举一反三.在以前的<用matlab扫扫雷>一文中,我用matlab简单的编写了一个扫雷小程序.当然,与Windows自带的扫雷程序自然是不敢相提并论.今天我就用c++来写个扫雷程 ...
- Android开发你不知道的TIPS
1.Space space是Android 4.0中新增的一个控件,它实际上可以用来分隔不同的控件,其中形成一个空白的区域.这是一个轻量级的视图组件,它可以跳过Draw,对于需要占位符的任何场景来说都 ...