代码
public class LazySingleton {
//私有构造方法
private static LazySingleton instance;
private LazySingleton(){};
public static LazySingleton getInstance(){
if (instance ==null){
instance = new LazySingleton();
}
return instance;
}
}
分析
优点:节省了内存(相对于饿汉式),只有在调用方法的时候才会实例化
缺点:线程不安全:当多线程的时候,可能会出现线程不安全的情况。当两个线程都进入if 条件的时候,可能会生成两个实例( 如何调试多线程及为何线程不安全)。
进阶
如何保证线程安全,既然这样的单例模式,在多线程情况下没有办法保证他是线程安全的,那么如何解决–synchronized这里我们使用synchronized来保证它在多线程的情况下是线程安全的,代码如下:
public class LazySingleton {
//私有构造方法
private static LazySingleton instance;
private LazySingleton() {
};
//使用synchronized 关键字来保证线程安全
public synchronized static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
这样的改造下,确实可以保证线程是安全的,但是会衍生新的问题:synchronized 在使用时会控制所有的线程,保证只会有一个线程访问到这个方法。假设现在有N多的线程,都要来调用这个方法。那么当有一个线程进入方法后,其余所有的线程都是等待的状态,会造成线程的阻塞。那有没有更好的办法呢?既然现在都阻塞在了方法外,那就让方法在进来之后再做判断:
public class LazySingleton {
//私有构造方法
private static LazySingleton instance;
private LazySingleton() {
};
public synchronized static LazySingleton getInstance() {
if (instance == null) {
//使用synchronized 关键字来保证线程安全
synchronized (DoubleCheckLazySingleton.class){
instance = new LazySingleton();
}
}
return instance;
}
}
这样就可以保证线程不会都阻塞在方法外,只有在判断是否生成实例的时候才会进行阻塞。这样可以有效的“过滤”掉部分问题。。但是又会产生新的问题:线程又双叒叕不安全了。。。那如果再保证线程安全呢?这时候就要进入一个大家都熟悉的地方了:双重校验
public class DoubleCheckLazySingleton {
//私有构造方法
private static DoubleCheckLazySingleton instance;
private DoubleCheckLazySingleton(){};
public static DoubleCheckLazySingleton getInstance(){
//检查是否要阻塞
if (instance == null){
synchronized (DoubleCheckLazySingleton.class){
//检查是否要重新创建实例
if (instance == null){
instance = new DoubleCheckLazySingleton();
}
}
}
return instance;
}
}
可以看到这里检测了两次instance==null,但是两次检测的作用不一样。一次是判断是否需要阻塞。如果已经生成了实例,那么所有的进程就没有必要阻塞,第二次判断,检查是否需要重新创建实例。
总结
懒汉式相对于饿汉式来说,虽然线程不安全,但是在后续做了某些操作后(双重校验),可以保证线程安全,不过代码量上明显多于饿汉式。
最后,还遗留了一个问题:指令重排序。这里就先不做讨论了。