在工作或者各种面试中,面试官都很爱问单例模式的懒汉模式之类的问题,在工作中,各种初始化(ActiveMQ等)都有可能用到,所有下面就具体讲一下单例模式的演化过程:
1. 一个最简单的单例模式:
class Singleton1{ private static Singleton1 singleton1 = null; public static Singleton1 newInstanceSimple(){ if(singleton1 == null){ singleton1 = new Singleton1(); } return singleton1; } }
但是这个代码不是线程安全的,如果两个线程(A,B)同时访问单例模式中的方法,如果线程A运行到第8行之后被线程调度器调度不再执行,这是线程调度器调度B运行这个方法,会创建一个单例对象,而线程调度器又调度A线程,A线程又创建一个单例对象,那么,内存中就有两个Singletion对象。
2.改进一下
class Singleton2{ private static Singleton2 singleton2 = null; public static synchronized Singleton2 newInstanceSimple(){ if(singleton2 == null){ singleton2 = new Singleton2(); } return singleton2; } }
这样虽然保证了线程安全,但是又会产生性能的问题,例如多个线程访问这个方法,返回singleton2对象的操作是很快的(就一条指令),但是synchronized关键字是很花性能的。
3.路漫漫,继续改进
class Singleton3 { private static Singleton3 singleton3 = null; public static Singleton3 newInstanceSimple() { synchronized (Singleton3.class) { if (singleton3 == null) { singleton3 = new Singleton3(); } } return singleton3; } }
虽然把synchronize关键字放在了函数内部,但是效率还是不怎么高,继续改
4. 下面这一招叫做双重锁定检查(DCL)
class Singleton4 { private static Singleton4 singleton4 = null; public static Singleton4 newInstanceSimple() { if (singleton4 == null) { synchronized (Singleton4.class) { if (singleton4 == null) { singleton4 = new Singleton4(); } } } return singleton4; }
除了第一次创建访问同步块,其他访问都都不需要访问同步块,达到要求了吗?
如下场景:
singleton4 = new Singleton4();并非原子操作()(原子操作的意思就是这条语句要么就被执行完,要么就没有被执行过,不能出现执行了一半这种情形)。事实上高级语言里面非原子操作有很多,我们只要看看这句话被编译后在JVM执行的对应汇编代码就发现,这句话被编译成8条汇编指令,大致做了3件事情:
1)创建内存给Singleton4
2)调用Singletion4的init()方法进行初始化
3)singleton4指向Singletion4内存
java编译的时候(早起编译还是晚期编译?)时候会进行指令重排序,很有可能顺序不是 1-2-3,如果是1-3-2,那么一个线程访问到第三步的时候,还没执行第二步,被线程调度器调度不再执行,然后另外一个线程执行,实例没有进行初始化,如果访问这个对象的一些方法,就会报错,那么怎么防止这种指令重排序呢,就用volatile关键字,但是最好保证在jdk1.5之后,代码如下:
class Singleton5 { private static volatile Singleton5 singleton5 = null; public static Singleton5 newInstanceSimple() { if (singleton5 == null) { synchronized (Singleton5.class) { if (singleton5 == null) { singleton5 = new Singleton5(); } } } return singleton5; }