单例模式介绍

单例模式算是平常用的比较多的,而且也比较简单的模式之一。单例模式的作用就是保证在程序中只有一个实例存在。

单例模式实现原理

单例模式需要保证在系统中只有一个实例,单例的对象就不能被随便创建。通常我们实例化对象通过 new 关键字来创建对象,如果用这种方式任何人想要该对象都可以通过 new 来实例化,这就不能保证单例模式的唯一性了。所以我们要让别人不能实例化该对象,该对象只能由我们实例化并提供给外部。实现这个只需要把构造方法私有化(private),由于我们私有了构造方法,所以外部不能访问到,于是我们需要通过静态方法来提供这个实例。在多线程的情况下还要考虑单例的对象会被多次创建。

实现单例有以下几个关键点:

  • 私有化构造方法
  • 通过静态方法提供单例对象
  • 确保在多线程情况下,单例对象只有一个

单例模式有许多不同的写法,每种写法各有特点,下面一一介绍。

饿汉式

饿汉式的单例模式在声明的时候就创建好了,用到的时候直接使用。构造函数被私有化了,确保了唯一性。要获得该实例只能通过 getInstance

饿汉式的缺点是只要系统存在就会创建这个对象,但是系统有可能会用不到,就会造成资源浪费。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Singleton{
  private static final Singleton INSTANCE = new Singleton();

  private Singleton(){

  }

  public static Singleton getInstance(){
    return INSTANCE;
  }

}

懒汉式

饿汉式的单例模式如果用不到就会造成资源的浪费,有没有在用到时才去创建的写法。有,懒汉式。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Singleton{
  private static final Singleton INSTANCE;

  private Singleton(){

  }

  public static synchronized getInsance(){
    if (null == INSTANCE) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }

}

在懒汉式的 getInstance 上加了 synchronized 这个关键字,这样可以确保在多线程的情况下保证单例的唯一性。

双重锁形式的懒汉式

懒汉式确保了在需要用到的才去实例化对象,避免了没有使用到而浪费资源的情况。但是为了确保在多线程的情况下单例唯一,在 getInstance 方法上加上了 synchronized ,这就带来了新的问题。即使 INSTANCE 已经被初始化了,调用 getInstance 还是会进行同步,这就导致了性能低下,虽然节约了资源,所以还可以进行优化。我们使用双重锁来去掉不必要的开销。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Singleton{
  private static final Singleton INSTANCE;

  private Singleton(){

  }

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

}

双重锁形式的单例和懒汉式最大的区别就是在 getInstance 方法上, getInstance 进行了两次判空,第一次判空避免了不必要的同步,第二次判断是为了在 null 情况下创建实例。 双重锁是不是没有缺点,当然不是,在 JDK1.5 之前这种方法并不能保证单例。第一次加载稍慢,在高并发环境下有很小的概率会失败。

静态内部类形式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Singleton {
  private Singleton(){

  }

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

  private static class SingletonHoler{
    private static final Singleton INSTANCE = new Singleton();
  }
}

第一次加载 Singleton 时,并不会初始化 INSTANCE ,只有当调用 getInstance() 才会初始化。调用 getInstance() Java 虚拟机会加载 SingletonHoler ,这种方式不仅能够保证线程安全,也可以保证单例的唯一性,也延迟了单例的实例化。

枚举单例

1
2
3
public enum Singleton{
  INSTANCE;
}

除了上面的几种写法,还可以通过枚举来实现单例。枚举的实例默认是线程安全的,还能保证唯一性。枚举的写法应该是所有写法中最简单的。

参考

  • 《Android 源码设计模式解析与实战》