不涉及多线程的情况下实现单例模式是非常简单的,有两种实现方式,Lazy 和 Eager 模式:
public class SingletonEager {
//Eager模式,当Singleton被ClassLoader加载的时候创建Singleton对象
private static SingletonEager _instance = new SingletonEager();
private SingletonEager(){
}
public static SingletonEager getInstance(){
return _instance;
}
}
public class SingletonLazy {
private static SingletonLazy _instance;
private SingletonLazy(){
}
//lazy模式,当用户真的需要实例时才会创建
public static SingletonLazy getInstance(){
if (_instance == null){
/*①*/
_instance = new SingletonLazy();
}
return _instance;
}
}
当多个线程同时调用单例时,Eager模式不会出现问题,因为每个线程只对 _instance进行读操作。但是Lazy模式会出现问题,因为各个线程不但对_instance有读操作,还有写操作,从而出现线程安全问题。
例如线程A如果执行到上述代码的①处,同时线程B开始执行if判断,发现_instance是null,这时线程A和B都会创建一个SingletonLazy对象。
按照这个逻辑,我们对上述代码进行修改,在getInstance方法上下入synchronized关键字,保证这个方法一次只能有一个线程调用。
public class SingletonLazy {
private static SingletonLazy _instance;
private SingletonLazy(){
}
//lazy模式,当用户真的需要实例时才会创建
public synchronized static SingletonLazy getInstance(){
if (_instance == null){
/*①*/
_instance = new SingletonLazy();
}
return _instance;
}
}
问题得到了解决。但是,这里会出现性能问题,因为不同线程不能同时对_instance进行写,但是可以同时读,所以不应该把
if (_instance == null) 和 return _instance 放入synchronized中。 再说具体点,不同的线程可以同时判断_instance是否为null,也可以同时返回_instance。所以我们可以对上述代码进行优化:
public class SingletonLazy {
private static SingletonLazy _instance;
private SingletonLazy(){
}
//lazy模式,当用户真的需要实例时才会创建
public static SingletonLazy getInstance(){
if (_instance == null){
/*①*/
synchronized(SingletonLazy.class){
_instance = new SingletonLazy();
}
}
return _instance;
}
}
这里之前的问题又出现了,如果线程A执行到①,与此同时线程B也执行到①(因为此时_instance 仍然为null)。所以我们可以在synchronized(this)之中加入if 判断即:d
public class SingletonLazy {
private static SingletonLazy _instance;
private SingletonLazy(){
}
//lazy模式,当用户真的需要实例时才会创建
public static SingletonLazy getInstance(){
if (_instance == null){
/*①*/
synchronized(SingletonLazy.class){
if (_instance == null){
_instance = new SingletonLazy();
}
}
}
return _instance;
}
}
So far so good!
那么问题来了,第一个if可以省略吗?
答案是可以,但这样会降低性能,因为所有线程执行if判断 都变成了同步的,假设此时_instance不为null了,其他线程应该可以同时执行这个判断。反之,如果_instance为null,那么所有线程都应该同步执行synchronized块。
接下来还有一个问题,JVM为了优化代码执行速度,会进行指令重排,以_instance = new SingletonLazy(); 这个语句为例,它其实可以分解为三个步骤:
1. 申请一块内存空间
2. 在这个空间实例化对象
3. 将引用指向个对象
指令重排是说上述三个步骤并不是按顺序执行的,而是可能先申请一块空间,然后将引用指向这块空间,最后在创建对象。如果是但线程,这么做肯定没问题,但是在多线程情况下,如果一个线程执行到第2步,此时_instance已经不为null, 而其他线程就直接返回这块内存空间,但是这时对象还未创建,如果在调用该对象的方法,就会抛出异常。所以此时我们要禁用指令重排,只需要将_instance声明为volatile。
public class SingletonLazy {
private volatile static SingletonLazy _instance;
private SingletonLazy(){
}
//lazy模式,当用户真的需要实例时才会创建
public static SingletonLazy getInstance(){
if (_instance == null){
/*①*/
synchronized(SingletonLazy.class){
if (_instance == null){
_instance = new SingletonLazy();
}
}
}
return _instance;
}
}
完。