“我这招江湖人称”双重检验锁“,够酷吧!”。
“老司机就是复杂,好端端的,被你弄了那么多个fileIO == null的判断,还用volatile关键字修饰fileIO,这样真的能提升性能吗?”
“you look,当有多有线程调用getInstance()方法的时候,不管三七二十一,先让他们进来。如果fileIO实例不为空,那最好了,直接return实例fileIO,跟synchronized一点都扯不上关系,所以也不会影响到性能。这是双重检验中的第一次检验。”
“oh,I know,如果fileIO是null的,就进入synchronized语句块,在synchronized语句块里面初始化对象。但为什么在synchronized语句中需要再次检查fileIO实例是否为null?”
“这就是第二次检验了,当有多个线程通过第一次检验时,假设线程拿到锁进入synchronized语句块,对fileIO实例进行初始化,释放FileIO.class锁之后,线程二持有这个锁进入synchronized语句块,此时又对fileIO对象就行初始化。所以在这里进行第二次检验防止这种意外发生。”
“我理解了,但我不明白fileIO为什么要用volatile关键字修饰?”
“我们假设线程一进入第二次检验之后就执行FileIO fileIO = new FIleIO()操作,在这个操作中,JVM主要干了三件事
1、在堆空间里分配一部分空间;
2、执行FileIO的构造方法进行初始化;
3、把fileIO对象指向在堆空间里分配好的空间。
但是,当我们编译的时候,编译器在生成汇编代码的时候会对流程顺序进行优化。优化的结果是有可能按照1-2-3顺序执行,也可能按照1-3-2顺序执行。
我们知道,执行完3的时候就fileIO对象就已经不为空了,如果是按照1-3-2的顺序执行,恰巧在执行到3的时候(还没执行2),突然跑来了一个线程,进来getInstance()方法之后判断fileIO不为空就返回了fileIO实例。
此时fileIO实例虽不为空,但它还没执行构造方法进行初始化。又恰巧构造方法里面需要对某些参数进行初始化。后来闯进来的线程糊里糊涂对那些需要初始化的参数进行操作就有可能报错奔溃了。”