设计模式(1):单例模式

155 阅读4分钟

单例模式

定义

确保单例类只有一个实例,并且这个单例类提供一个函数接口让其他类获取到这个唯一的实例。

为了确保只有一个实例,我们声明私有的构造方法,并使用static创建一个静态实例。这样就保证了其他地方没法使用构造方法,永远最多只有一个实例。当某个类的创建需要消耗大量资源,或者用来控制某些共享资源(数据库或者文件)的时候就可以使用单例模式。

另外单例模式又有两种实现方式。

饿汉模式:

即在声明的时候就将其进行初始化,这种是最简单的方式并且天生线程安全,但是不推荐,因为这种方式在类加载的时候就会进行初始化,一般来说使用单例模式的类占用资源都不少,这种方式浪费内存,尽量等到使用的时候再将其初始化,代码如下:

public class Singleton { 
    private Singleton() {} //私有的空构造方法 
    private static final Singleton instance = new Singleton1(); //创建静态的且唯一的实例 
    public static Singleton getInstance() {
        return instance; 
    }
}

懒汉模式

在声明的时候并不初始化,而是在第一次使用getInstance() 时进行初始化,这样就避免了饿汉式的弊端。

懒汉式的实现也有好几种方式,接下来依次介绍,除了最后一种外都各自有一些问题。

懒汉式,线程不安全

这种方式是最基本的实现方式,但是这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized所以会出现多线程问题。

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

懒汉式,线程安全

这种方式能够在多线程中很好的工作,但是使用了synchronized效率很低,也不推荐。

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

双检锁/双重校验锁(推荐)(volatile是关键)

这种方式采用双锁机制(volatilesynchronized),安全且在多线程情况下能保持高性能。实际上我们只有在第一次使用 getInstance() 进行初始化时才会产生多线程问题,因此后面的 getInstance() 操作不应该进行多线程安全的保护,不然会损失大量的性能。

首先第一个 if 判断语句确保了只有未进行初始化才进行加锁初始化,第二个 if 确保了只初始化一次(if判断和new操作不是原子操作)。

同时 volatile 确保了不会因为cpu指令重排而得到未初始化的实例(new不是原子操作):new操作符实际上可以分成三个步骤:

  1. 分配内存
  2. 初始化对象
  3. 将引用指向内存地址 如果不使用volatile,则有可能出现1-3-2的情况,当执行完第二步,这时候另一个线程调用 getInstacne() ,那么在第一个if判断语句中就不会判空,会将一个没有完成初始化的实例返回给这个线程。
public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
        }  
    }  
    return singleton;  
    }  
}

静态内部类实现

通过静态内部类也可以实现单例模式,而且和使用双重锁校验一样,不会出现多线程问题而且也会进行延迟初始化,同时代码也比较清晰易懂,但是同样也有无法传参的问题。

public class SingleTon {

    private SingleTon() {} //私有的构造方法

    //获取实例的静态方法

    public static SingleTon getInstance() {
        return SingleTonHolder.INSTANCE;
    }

    //静态内部类
    private static class SingleTonHolder {
        private static final SingleTon INSTANCE = new SingleTon();
    }
}

使用静态内部类,并不会在类加载的时候就进行初始化,会在第一次对其进行使用的时候进行初始化,这样就实现了延迟初始化。同时,由于其静态的特性,永远只有一个线程会执行其代码,因此也保证了初始化时的线程安全。

但是其有一个致命的缺点,由于是静态内部类的形式去创建单例的,故外部无法传递参数进去,例如Context这种参数,所以,我们创建单例时,可以根据使用场景自行选择。