---
单例模式
---

#### 目录

1. 思维导图
2. 定义和使用场景
3. 实现方式
4. 反序列化和反射
5. 优缺点

#### 思维导图

![](https://i.loli.net/2019/01/06/5c316f61d1c06.png)

#### 定义和使用场景

定义：

确保某一个类只有一个实例，而且自行实例化并向整个系统提供这个实例。

使用场景：

确保某个类有且只有一个对象的场景，避免产生多个对象消耗过多的资源。

#### 实现方式

##### 懒汉式

```java
public class SingleTon {
    private static SingleTon mInstance;

    private SingleTon() {

    }

    public static SingleTon getInstance() {
        if (mInstance == null) {
            mInstance = new SingleTon();
        }
        return mInstance;
    }
}
```

##### 饿汉式

```java
public class SingleTon {
    private static SingleTon mInstance = new SingleTon();

    private SingleTon() {

    }

    public static SingleTon getInstance() {
        return mInstance;
    }
}
```

##### DCL

```java
public class SingleTon {
    private static volatile SingleTon mInstance;

    private SingleTon() {

    }

    public static SingleTon getInstance() {
        if (mInstance == null) {	
            synchronized (SingleTon.class) {
                if (mInstance == null) {
                    mInstance = new SingleTon();
                }
            }
        }
        return mInstance;
    }
}
```

在 getInstance 方法里进行了两次判空：第一次判空是为了避免不必要的同步，第二次判空是为了在 null 的情况下创建实例。

之所以用 volatile 修饰 mInstance，那是因为 mInstance = new SingleTon() 并不是一个原子操作，它大致做了三件事：

1. 给 mInstance 分配内存
2. 调用其构造方法，初始化成员字段
3. 将 mInstance 对象指向分配的内存空间（此时 mInstance 就不是 null 了）

但是由于 CPU 的优化操作，可能会对这几个操作进行乱序执行，第二条和第三条的操作的执行顺序无法保证。如果线程 A 执行到 getInstance() 方法，进入锁块，先执行 第三条操作，然后另外一个线程 B 也执行到 getInstance() 方法，发现 mInstance 不为空了，就直接取走了 mInstance 对象，在使用时就会出错，这就是 DCL 失效问题。而 volatile 能禁止指令重排序。

##### 静态内部类

```java
public class SingleTon {
    private static volatile SingleTon mInstance;

    public static SingleTon getInstance() {
        return SingleHolder.singleTon;
    }

    static class SingleHolder {
        private static SingleTon singleTon = new SingleTon();
    }
}
```

当第一次加载 SingleTon 类的时候并不会初始化 mInstance，只有在第一次调用 getInstance 方法时才会导致 mInstance 被初始化。因此，第一次调用 getInstance 方法会导致虚拟机加载 SingleHolder 类，这种方式不仅能够确保线程安全，也能够保证单例对象的唯一性，同时也延迟了单例的实例化，这是推荐使用的单例模式实现方式。

类加载的执行 JVM 可以保证是线程安全的。

##### 枚举类

```java
public enum SingleTonEnum {
    INSTANCE;
    public void doSomething(){
        
    }
}
```

枚举在 Java 中与普通的类是一样的，不仅能够有字段，还能有自己的方法。最重要的是默认枚举实例的创建是线程安全的，并且在任何情况下它都是一个单例。

#### 反序列化和反射

##### 反序列化

对于上面的实现方式，除了枚举，都无法避免反序列化。反序列化创建对象是不走构造函数的，所以构造函数是私有化完全没用，要杜绝单例对象被反序列化时重新创建对象，则必须加入如下方法：

```java
    private Object readResove() throws ObjectStreamException {
        return mInstance;
    }
```

##### 反射

直接在构造方法中抛异常。

```java
private SingleTon(){
	throw new RuntimeException("SingleTon can't be reflected");
}
```



#### 优缺点

优点：

由于只有一个实例，，故可以减少内存开销，避免对资源的多重占用。

缺点：

单例类扩张困难，职责过重，一定程度上违背 “单一职责原则”。

注意单例对象可能造成的内存泄露问题！