单例模式的几种写法

单例模式是平时开发中使用的最多的一种设计模式,虽然简单,但是写法却很多,下面是常见的几种单的写法。

第一种(懒汉,线程不安全)

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

这种写法是线程不安全的,多线程情况下有可能会创建多个实例,不推荐使用。

第二种(懒汉,线程安全)

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

这种写法是第一种的线程安全版,使用synchronized保证了多线程情况下只会有一个实例被创建,但是synchronized是悲观锁,一个线程访问时,会阻塞其他的线程,因此效率比较低。

第三种(饿汉)

1
2
3
4
5
6
7
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}

饿汉模式的写法简单,但是在类装载的时候就创建了对象的实例,无法做到延迟创建对象。

第四种(静态内部类)

1
2
3
4
5
6
7
8
9
10
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

采用静态内部类的形式,直接用jvm类加载机制处理异步加锁的问题,减小了内存消耗。

这个解决方案被称为Lazy initialization holder class 模式,这个模式综合使用了java的类级内部类和多线程缺省同步锁的知识, 很巧妙的同时实现了延迟加载和线程安全。

  1. 相应的基础知识

    • 什么是类级内部类?简单点说,类级内部类指的是,有static修饰的成员内部类。如果没有static修饰的成员式内 部类被称为对象级内部类。
    • 类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此 可以直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
    • 类级内部类中,可以定义静态的方法。在静态方法中只能引用外部类中的静态成员方法或变量。
    • 类级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载。
  2. 多线程缺省同步锁的知识: 在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制,但是在某些情况下,JVM已经隐含的为您执行了同步,这些情况下就不用自己再来进行同步控制了。 这些情况包括:

    • 由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
    • 访问final字段时
    • 在创建线程之前创建对象时
    • 线程可以看见它将要处理的对象时
  3. 解决方案的思路

    • 要想很简单的实现线程安全,可以采用静态初始化器的方式,它可以由JVM来保证线程的安全性。比如前面的饿汉式实现方式。但是这样一来,不是会浪费一定的空间吗?因为这种实现方式,会在类装载的时候就初始化对象,不管你需不需要。
    • 如果现在有一种方法能够让类装载的时候不去初始化对象,那不就解决问题了?一种可行的方式就是采用类级内部类,在这个类级内部类里面去创建对象实例。这样一来,只要不使用到这个类级内部类, 那就不会创建对象实例,从而同步实现延迟加载和线程安全。
  4. 补充说明下他是如何体现 懒加载的(Lazy initialization):

因为内部静态类是要在有引用了以后才会装载到内存的。所以在你第一次调用getInstance()之前,SingletonHolder是没有被装载进来的,只有在你第一次调用了getInstance()之后,里面涉及到了return SingletonHolder.instance; 产生了对SingletonHolder的引用,内部静态类的实例才会真正装载。这也就是懒加载的意思

  1. 关于 JVM来保证线程的安全性 这句话的意思:

利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗。

第五种(枚举)

1
2
3
4
5
public enum Singleton {
INSTANCE;
public void doSomeThing() {
}
}

枚举单例的好处有三个:线程安全,不会因为序列化而产生新的实例,防止反射攻击。由于安卓开发不推荐使用枚举,这种写法虽然好处很多,但是在Android开发中很少见。

第六种(双重校验锁,DCL)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}
}

这种写法考虑了线程安全,将对singleton的null判断以及new的部分使用synchronized进行加锁。同时,对singleton对象使用volatile关键字进行限制,保证其对所有线程的可见性,并且禁止对其进行指令重排序优化。