单例设计模式

单例设计模式是各种设计模式中最简单的,但是实际编码过程中使用最多的模式;面试中也经常被问到。我们来review一下单例设计模式

饿汉模式

public class Apple {
    private static Apple instance = new Apple();

    private Apple(){}

    public static Apple getInstance(){
        return instance;
    }
}

饿汉模式会在类加载的时候就初始化实例,而非使用时

懒汉模式

public class Apple {
    private static Apple instance = null;

    private Apple(){}

    public static Apple getInstance(){
        if(instance==null){
            instance = new Apple();
        }
        return instance;
    }
}

懒汉模式下,实例会在使用时再去初始化;但是这种懒汉模式有线程安全问题,多线程情况下可能被创建多个实例。

线程安全的懒汉模式

public class Apple {
    private static Apple instance = null;

    private Apple(){}

    public static synchronized Apple getInstance(){
        if(instance==null){
            instance = new Apple();
        }
        return instance;
    }
}

上面的带锁的懒汉模式解决了线程安全的问题,但是效率不高,每次只允许一个线程获取实例。

双重校验的懒汉模式

public class Apple {
    private static Apple instance = null;

    private Apple(){}

    public static Apple getInstance(){
        if(instance==null){			//第1次检查
            synchronized (Apple.class){
                if(instance==null)	//第2次检查
                    instance = new Apple();
            }
        }
        return instance;
    }
}

第1次检查在synchronized外面,然后同步锁住代码块;第2次检查是为了防止在初始实例化的时候,线程B在同步块等待,线程A已经进入同步块并初始化了实例,等A退出同步块,线程B进入同步块不需要在初始实例。

上述双重校验还是有问题,原因是new Apple()并不是原子操作,实际上是分成3个步骤。

  1. 给 instance 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量
  3. 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

由于这三个步骤可能按照1-3-2来执行,当3执行完了instance就非null了,这时线程B执行getInstance就会获得对象并使用就会报错。具体的解释参考左耳朵耗子的博文

双重校验版本2

public class Apple {
    private static volatile Apple instance = null;

    private Apple(){}

    public static Apple getInstance(){
        if(instance==null){
            synchronized (Apple.class){
                if(instance==null)
                    instance = new Apple();
            }
        }
        return instance;
    }
}

使用volatile主要目的是禁止指令重排序,让new Apple()按照1-2-3的步骤执行。这样就能防止上述双重校验版本1的问题

静态内部类方法

public class Apple {

private Apple(){}

public static  Apple getInstance(){
    return AppleHolder.instance;
}
public static class AppleHolder{
    private static Apple instance = new Apple();
}

}
静态内部类的方法,是JVM机制保证线程安全;当多个线程同时getInstance时,AppleHolder类的加载是JVM加载的,不会有线程问题;又是懒汉模式在需要时初始化。

总结

单例设计模式,有4中方式:饱汉式、懒汉式、双重校验方式、静态内部类方式。其中双重校验方式还有优化版本,在实际变成开发中推荐使用静态内部类的方式。

博主原创,转载请标明出处!
联系方式: 微信:corolla_zhaojd
Email: zhaojiandongzju@gmail.com

原文地址:https://www.cnblogs.com/oldtrafford/p/6820716.html