【面试题1 : 实现Singleton 模式——七种实现方式】

单例模式是最常用也是最基础的一种模式,我们来好好地总结一下吧。

目录
1. 第一种实现方式-(饿汉,常用)
2. 第二种实现方式-(懒汉,不安全)
3. 第三种实现方式-(加锁的懒汉,性能低)
4. 第四种实现方式-(静态块,可以)
5. 第五种实现方式–(静态内部类,推荐)
6. 第六种实现方式-(枚举,推荐)
7. 第七种实现方式-(重要,面试)

1. 第一种实现方式-饿汉,常用

/**
 * 单例模式,饿汉式,线程安全
 */
public static class Singleton01{
    private static Singleton01 instance = new Singleton01();

    private Singleton01(){}

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

饿汉,只要有就先加载出来让我吃。

首先是写明私有的构造方法防止被new,然后直接就实例化,最后调用,不存在线程安全问题。

2. 第二种实现方式-懒汉,不安全

/**
 * 单例模式,懒汉式,线程不安全
 */
public static class Singleton02{
    private Singleton02(){}

    private static Singleton02 instance = null;

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

懒汉,就是我饿的时候再找吃的,属于懒加载,在instance = new Singleton02();可能会出现线程安全问题,因为new一个对象在JVM底层做了如下工作:

  • 给 instance 分配内存

  • 调用 Singleton 的构造函数来初始化成员变量

  • 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

显然是不能保证原子性的。

3. 第三种实现方式-加锁的懒汉,性能低

/**
 * 单例模式,懒汉式,线程安全,多线程环境下效率不高
 */
public static class Singleton03{
    private Singleton03(){}

    private static Singleton03 instance = null;

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

既然提到不安全,那么就加锁,显然是可以解决线程安全性问题的,但是效率会比较低。

4. 第四种实现方式-静态块,可以

/**
 * 单例模式,懒汉式,变种,线程安全
 */
public static class Singleton04{
    private Singleton04(){}

    private static Singleton04 instance = null;

    static {
        instance = new Singleton04();
    }

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

当第一次引用getInstance()方法的时候,访问静态内部类中的静态成员变量,此时该内部类需要调用static代码块(因为首次访问该类)。而后再次访问getInstance()方法会直接返回instace引用。这种做法相对于传统做法更加巧妙。

5. 第五种实现方式–静态内部类,推荐

/**
 * 单例模式,使用静态内部类,线程安全【推荐】
 */
 public static class Singleton05{
     private Singleton05(){}

     private static final class SingletonHolder{
         private static final Singleton05 instance = new Singleton05();
     }

     public static Singleton05 getInstance(){
         return SingletonHolder.instance;
     }
}

定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。而类型为SingletonHolder的类,只有在Singleton.getInstance()中调用,由于私有的属性,他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例。
优点:达到了lazy loading的效果,即按需创建实例。

6. 第六种实现方式-枚举,推荐

/**
 * 静态内部类,使用枚举方式,线程安全【推荐】
 */
public enum Singleton06{
     INSTANCE;
}

在《Effective Java》最后推荐了这样一个写法,简直有点颠覆,不仅超级简单,而且保证了现场安全。这里引用一下,此方法无偿提供了序列化机制,绝对防止多次实例化,及时面对复杂的序列化或者反射攻击。单元素枚举类型已经成为实现Singleton的最佳方法。

很多人会对枚举法实现的单例模式很不理解。这里需要深入理解的是两个点:

  • 枚举类实现其实省略了private类型的构造函数
  • 枚举类的域(field)其实是相应的enum类型的一个实例对象

对于第一点实际上enum内部是如下代码:

public enum Singleton {
    INSTANCE;
    // 这里隐藏了一个空的私有构造方法
    private Singleton () {}
}

比较清楚的写法是:

public class SingletonExample5 {
    private SingletonExample5(){}

    public static SingletonExample5 getInstance(){
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton{
        INSTANCE;

        private SingletonExample5 singleton;

        //JVM保证这个方法绝对只调用一次
        Singleton(){
            singleton = new SingletonExample5();
        }

        public SingletonExample5 getInstance(){
            return singleton;
        }
    }
}

7. 第七种实现方式-重要,面试

/**
 * 静态内部类,使用双重校验锁,线程安全【推荐】
 */
 public static  class Singleton07{
     private Singleton07(){}

     private volatile static Singleton07 instance = null;

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

思想:先判断一下是不是null,然后加锁,再判断一下是否为null。如果还是null,则可以放心地new。

还是不大明白为什么要判断两次null?

第一次null很简单,如果不为null,直接return,不需要进锁了。如果为null,说明有可能是第一次进来。

这里第一次进来的线程可能不止一个,假设是两个,分别为线程A和线程B,那么进行排队,假设A先进来,如果没有Null判断,那么它就直接new,释放锁没这个时候B获得锁,还是直接new,不就出现了两个对象了吗?

猜你喜欢

转载自blog.csdn.net/sunweiguo1/article/details/80329062