有什么作用?
两个作用:
1.保证线程可见性
当一个线程对共享变量进行修改的时候,另一个线程要对这个共享数据进行读取或者修改的时候,会发现这个共享变量已经被人修改了
2.禁止指令重排序
首先要知道什么是重排序
当在执行代码的时候,cpu的工作内存会从主存加载到变量进来,当遇到很多的变量都需要进来的时候,因为cpu的工作内存中空间有限,他会将最开始读到的变量写回到主存中,当再次用到这个变量的时候,会重新读取到工作内存,效率会很低。所以就有一个解决方案:
他会重新排序,但是他也不是瞎排序,会基于happey-before原则(在不影响代码执行结果的前提下)才会重排序,这样的话就不用重新从主存读到工作内存了,提高效率。
那既然重排序是好的,那为什么还要禁止指令重排序?
单线程的时候重排序肯定是没问题的,但是多线程的话就会有问题!
现在问大家一个问题,new对象在JVM中会分为哪几步?
1.为new的对象在堆中分配一块内存空间
2.根据这个对象类的构造器,进行初始化,放到刚才分配的堆空间里面
3.栈中的引用指向这个堆中的对象(这一步完成了,那么这个对象就!=null了)
用单例模式(一个程序中只能有却只能有一个对象实例)的代码来举例
public class Singleton {
//volatile的作用是 防止指令重排序,多线程情况下,cpu工作内存重新排序,new出一个半成品的对象,导致报错
private static volatile Singleton INSTANCE;
private Singleton(){}
/**
* 单例模式中运用的dubbocheck方案
* 两个if中间加锁
* @return
*/
public static Singleton getInstance(){
//可能由于重排序问题,这个INSTANCE!=null
if(INSTANCE == null){//这里的第一个if 防止多线程情况下频繁加锁造成的性能问题
synchronized (Singleton.class){
if(INSTANCE == null){//这里的if是防止多线程情况下new出多个对象
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
在多线程情况下,new对象的顺序会由于指令重排序,1->3->2(参考上面new对象的步骤),第一个线程可能先执行3再执行2,但是可能还没有执行 2 ,这时候第二的线程进来判断 if INSTANCE!=null,所以会创建出来一个半成品的对象,导致程序报错。
可以解决线程安全吗?
不是线程安全的,因为他不具备原子性
解释一下什么是原子性?
原子性就是一批线程执行任务的时候,不会被其他线程所干扰,要么同时成功,要么同时失败。
比如说在多线程执行i++的操作的时候,实际的结果是少加的情况,具有并发问题,这就是原子性导致的
他是如何保证可见性的?
1. cpu工作内存读取主存后,经过计算后的结果,是通过异步的形式刷到主存中的,被volatile修饰的变量会马上同步刷新到主存中。(MESI协议)
2.总线会马上通知其他cpu内核,变量失效了,其他cpu会重新从主存中读取到最新的数据
为什么不具备原子性?
通过可见性的原理知道,线程改完同步写到主存并且通知其他线程的操作是有延迟的, 他只能尽可能的让其他线程读区到最新的数据,但是阻止不了别人改。所以不具备原子性
他是怎么进行指令重排序的?
内存屏障(面试的时候记住这个词就可以了,里面太深奥了,就算是架构师他也不一定明白原理!)