【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)

(一)volatile有三大特性:保证可见性;不保证原子性;禁止指令重排。而这三个概念对应的JMM中的可见性、原子性、有序性,所以首先要了解JMM。
(1)线程将修改后的值从自己的工作内存写回主内存改变主内存值后,及时通知其他线程的机制就是JMM中可见性的概念。
(二)volatile可见性代码证明:
(1)代码截图:
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第1张图片
(2)解释代码设计思路:
①首先写了个资源类MyData,有一个属性number,和一个改变number的方法。用于多线程操作。
②写一个main方法,其中main方法就是一个主线程,在主线程中先实例化一个MyData资源类的对象,再在main线程中再new 一个线程AAA,线程AAA让他先输出进来,再暂停3秒,再将主线程的资源类MyData的对象的number由0改为60,再输出改完了。
③在主线程中,在子线程后写代码实现读取资源类MyData的对象的number,当number等于0时一直等待,否则输出主线程的名称。
④这种设计由于子线程等了3秒改值,那么主线程while代码块读到number是在子线程改number之前的。再由可见性定义(线程将修改后的值从自己的工作内存写回主内存改变主内存值后,及时通知其他线程的机制就是JMM中可见性的概念)我们可知,如果现在这种场景有可见性,那么当子流程改完值后主线程while是会被通知到改值然后切为新的值那么主线程会及时终止的。
(3) 执行查看效果:【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第2张图片
备注:可以看到,一直卡着不执行主线程的输出线程名。
(4)将资源类Mydata的number加上volatile关键字代码如下:
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第3张图片
(5)查看加volatile后的执行效果:
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第4张图片
(6)由(1)-(5)的示例可证明volatile具有可见性。
(三)volatile不保证原子性验证。为什么volatile不能保证原子性?如何保证原子性?
(1)原子性指的是什么?
不可分割,完整性。某个线程在做某个某个具体业务时,中间不可以被加塞或者分割。需要整体完整,要么同时成功,要么同时失败。
(2)举例
①设计起20个线程,每个线程执行number++ 1000次。如果是原子性按照定义那么结果一定为20000。
②代码与执行结果:
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第5张图片
(3)为什么volatile在上述例子中不能保证原子性?
讲师给的原因是:number++ 实际是分成三部分——获取number的初始值、加1,写回主内存。比如有线程1和线程2,number原始值为0,线程1和线程2都先做了第一步获取number初始值0,然后线程2被挂起了,然后线程1执行加一然后写回主内存然后发通知给其他线程,一段时间后线程2被唤醒,没收到通知做了加1和写回主内存,这时候最终number只加了1。也就是说虽然线程1写回后通知了其他线程,但是由于此时其他线程为被挂起状态,没有收到。就会导致2个线程都对number进行+1而最后实际只加了1.
(4) 如何保证原子性?
①加synchronized,不过太重了。
②使用JUC的AtomicInteger,然后用它提供的一些方法。很好理解既然是上述普通的int类型的number做number++时是三步且volatile也保证不了原子性,那就用AtomicInteger和它的方法 来保证加加这个业务的原子性。至于为什么AtomicInteger能做到讲师说下下次讲。
举例:
代码修改新增如下图:
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第6张图片
执行查看结果:
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第7张图片
(四)volatile为什么能保证有序性,即介绍下它的禁止指令重排(这一小节讲师讲的不是很好我有一些疑问)
(1)介绍指令重排
计算机在执行程序时,为了提升性能,编译器和处理器常常会对指令进行重排,一般分以下三种(编译器优化的重排、指令并行的重排、内存系统的重排):
在这里插入图片描述
单线程环境里面确保程序最终执行的结果和代码顺序执行的结果一致。(根据(五)分析这是指令重排需要遵循的规则,指令重排只会保证串行语义的执行的一致性(单线程),不会关心多线程间的语义一致性)
处理器在进行指令重排序时必须要考虑指令之间的数据依赖性。
比如下面的例子重排后不能是4变成第一条执行的。
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第8张图片
多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保持一致性是无法确定的,结果无法预测(这句话应该是在陈述指令重排导致程序异常的场景)
指令重排后代码书写顺序与最后执行顺序不一定一致,就是对执行顺序做重排。
(2)多线程指令重排可能导致的问题
例子1:
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第9张图片
例子2:
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第10张图片
备注:这个场景是假设有俩线程线程1和线程2分别执行该资源类对象的method01和method02,在指令不重排的情况下,method2的输出语句不会是5,而如果指令重排,就会导致一种情况,假设method01被重排成先flag = true;然后a=1; 然后线程1先执行了flag = true然后线程2判断条件成立进入代码块,然后执行a=a +5 = 0+5=5然后输出 *****retValue:5。
(3)volatile禁止指令重排
volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的线程。

先了解一个概念,内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:
一是保证特定操作的执行顺序。
二是保证某些变量的内存可见。(volatile就是利用该特性实现内存的可见性)
由于编译器和处理器都能执行指令重排优化。如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重新排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本。

【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第11张图片
可以看到是四种屏障,storestore屏障、storeload屏障、loadload屏障、loadstore屏障。
我自己提出的问题:我对于上面这张图不了的点是图左侧他描述说 会在写操作后加入一条store屏障指令,首先没说主语,是谁写操作后,普通写还是volatile写还是都有?然后图中明显是俩屏障。图右侧描述也有相同的问题。
(五)某些单例模式在多线程情况下存在安全问题,请举例?如何解决掉安全问题?
(1)某些单例模式在多线程情况下存在的安全问题,举例如下。
①写一个简单的单例
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第12张图片
②构造多线程场景复现安全问题,前提说明如果线程安全的那么多线程都执行SingletonDemo.getInstance();最终也应该只有一个”进入构造方法”语句被输出到控制台。
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第13张图片
③效果:
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第14张图片
④结论:某些单例模式在多线程情况下存在的安全问题
⑤其他:下面给出例子证明单例模式在单线程下是有效的。
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第15张图片
(2)如何解决安全问题。
①对getInstance方法加synchronized。讲师说可以解决。但是太重了。
②DCL(double check lock 双端检锁机制)即加锁前和加锁后做两次判断。例子如下
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第16张图片
注意上面这种情况写法还是有安全问题!!!因为多线程有指令重排!!!需要加入volatile禁止指令重排!!!
原因分析:
instance = new SingletonDemo(); 这行代码其实要分成三个步骤做。
memory = allocate();//1.分配对象内存地址
instance(memory);//2.初始化对象
instance = memory;//3.设置instance指向刚分配的内存地址,注意此时instance != null

由于2 和 3 不存在数据依赖关系,并且在单线程情况下无论重排前还是重排后程序的执行结果没有改变,因此这种重排优化是允许的。
当指令重排时比如顺序变成1,3,2就会出现一个线程完成了1和3 然后第二个线程进入了getInstance()方法,由于此时instance != null ,所以不会重新new SingletonDemo()。然后直接返回了instance,然而这个地址上还没有对象所以拿到的是空。

可以这样理解,第一步是要为这个对象在内存中开辟一个地址,第二步是在这个地址上把这个对象做出来,第三步是将instance指向这个地址。如果顺序变成132就是先对象在内存中开辟一个地址,然后instance指向这个地址,然后第二个线程执行的是return instance那去这个地址拿东西由于还没有执行步骤2拿到的其实还是空。
为了防止这个情况需要对instance对象加volatile即:
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第17张图片
即最终解决方案是volatile加双端检锁机制:
【面试题】volatile是Java虚拟机提供的轻量级的同步机制(可以理解为轻量级synchronized),请你具体说下。(以及关于volatile的其他知识点)_第18张图片

你可能感兴趣的:(面试题剖析,java,面试,开发语言)