设计模式系列之单例模式

为什么使用单例模式?

  1. 某些类创建比较繁琐耗时,频繁创建增加系统开销
  2. 省去了new操作符,降低了系统内存的使用频率,减轻GC压力
  3. 某些情况下要求该类有且仅有一个实例对象在当前JVM中 ,如资源管理相关 线程池 日志对象

特点

  1. 单例类只能有一个实例。
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例类必须给所有其他对象提供这一实例。

两种单例实现

懒汉式

懒汉式,只有当调用getInstance的时候,才会去初始化这个单例

1
2
3
4
5
6
7
8
9
10
11
12
//懒汉式单例类.在第一次调用的时候实例化自己
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}

此实现存在线程安全问题,有以下几种解决方案

  1. 在getInstance()方法上加synchronized

    1
    2
    3
    4
    5
    6
    public static synchronized Singleton getInstance() {
    if (single == null) {
    single = new Singleton();
    }
    return single;
    }

    此种方式把锁加在方法上,这样每次调用此方法都会锁定整个对象,效率会比较低,实际上只有当单例不存在需要new的时候才需要上锁

  2. 双重检查锁定

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

    在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗

  3. 静态内部类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public class Singleton {
    /* 私有构造方法,防止被实例化 */
    private Singleton() {
    }
    /* 此处使用一个内部类来维护单例 */
    private static class SingletonFactory {
    private static Singleton instance = new Singleton();
    }
    /* 获取实例 */
    public static Singleton getInstance() {
    return SingletonFactory.instance;
    }
    }
    ```
    > 此种方式 不需要同步来解决线程问题,而是利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,又避免了同步带来的性能影响。
    ## 饿汉式
    > 饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的,而且天生是线程安全的
    ```java
    //饿汉式单例类.在类初始化时,已经自行实例化
    public class Singleton1 {
    private Singleton1() {}
    private static final Singleton1 single = new Singleton1();
    //静态工厂方法
    public static Singleton1 getInstance() {
    return single;
    }
    }

区别

  1. 线程安全:
    饿汉式天生就是线程安全的,可以直接用多线程环境而不出问题
    懒汉式本身是非线程安全的,可以通过上述1、2、3方法解决
  2. 性能
    饿汉式在类创建的同时就实例化出一个静态对象出来,不管以后是否使用都占用着内存,但是在其第一次调用速度也会非常快
    懒汉式会延迟加载,只有在第一次使用的时候才会进行初始化,如果需要做的工作比较多,性能上会有影响
关注我的微信,共同分享与讨论!