单例模式

单例模式


概念

有时对于系统中某些类而言,不需要多个实体。一方面需要保证这个实体的“原子性”,另一方面也可以节省系统资源。比如对于游戏账号这个类来说,账号里某些信息是恒定不变的,用户上线下线登陆的还是那个账号,这时可以考虑单例模式。

  • 定义: 单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。 结构图如下:
     单例模式

实例

对于单例模式而言,要求我们在系统中只能获得一个实例。很容易想到以下写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 有线程安全问题
*/
public class SimpleSingleton {
private static SimpleSingleton instance = null;
private SimpleSingleton () {
}
public static SimpleSingleton getInstance () {
if (instance == null) {
instance = new SimpleSingleton();
}
return instance;
}
}

首先将此类构造器设置为访问权限设置为private,禁止外部创建此对象。然后建立个私有静态成员变量instance存储实例,最后建立共公有静态成员方法,返回实例。

但是在高并发访问环境下,还是可能会出现多个实例的情况。原因是当第一次调用getInstance()时并触发instance=null时,程序执行instance = new SimpleSingleton(),如果SimpleSingleton对象初始化时间足够长,并且外部又出现第二次调用getInstance(),此时由于SimpleSingleton对象还在初始化中,还是会触发instance=null,进入循环体第二次执行instance = new SimpleSingleton()。

下面介绍三种单例模式都可以从各方面来解决这个问题。

  • 恶汉式单例类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 饿汉单例
    */
    public class EagerSinleton {
    private final static EagerSinleton instance = new EagerSinleton();
    private EagerSinleton () {
    }
    public static EagerSinleton getInstance() {
    return instance;
    }
    }

恶汉单例模式主要是通过final关键字来实现的,当类EagerSinleton加载时静态变量instance执行初始化,final可以保证instance的指向不会改变。缺点是类加载时实例就会创建,可能会对系统资源开销造成影响。

  • 懒汉式单例类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 懒汉单例
*/
public class LazySinleton {
private volatile static LazySinleton instance = null;
private LazySinleton () {
}
public static LazySinleton getInstance () {
if (instance == null) {
synchronized (LazySinleton.class) {
if (instance == null) {
instance = new LazySinleton();
}
}
}
return instance;
}
}

可以看到懒汉单例模式就是上述SimpleSingleton的线程安全版本。当需要此对象时对象才会被创建,也就是所谓的懒加载(LazyLoad)。

在getInstance方法中用synchronize关键字对LazySinleton.Class对象加锁,防止该对象同时被两个线程访问。instance采用volatile关键字修饰,保证此变量的对其他的线程可见性。多加了一层判断保证程序稳定性。缺点是必须处理线程锁,初次加载时可能消耗性能。

  • Initialization Demand Holder (IoDH)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * Initialization Demand Holder (IoDH)
    */
    public class Singleton {
    private Singleton () {
    }
    private static class HolderClass {
    private final static Singleton instance = new Singleton();
    }
    public static Singleton getInstance () {
    return HolderClass.instance;
    }
    }

上述单例采用静态内部类的方式来实现,依赖于java语言特性,将恶汉,懒汉特性结合。

总结

缺点
  • 职责过重,在一定程度上违背单一职责原则。
  • 对于带GC回收机制的语言来说,长时间不用的对象有可能被回收,可能导致单例对象状态丢失。
适用场景
  • 系统只需要一个实例