单例模式(Singleton)

First Post:

Last Update:

单例模式(Singleton)

单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是:让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。

单例模式结构图

单例模式代码结构

单例模式经典实现

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
单例模式代码结构
class Singleton
{
private static Singleton instance;
/// <summary>
/// 构造方法让其私有化,这样就堵死了外界用new创建该类新实例的可能
/// </summary>
private Singleton()
{

}
/// <summary>
/// 此方法是获取本类的唯一全局访问点
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
//若实例不存在,则new一个实例,否则直接返回已有实例
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}

//客户端代码:
Singleton s1 = Singleton.GetInstance();
Singleton s2 = Singleton.GetInstance();
if (s1 == s2)
Console.WriteLine("两者相同");
else
Console.WriteLine("两者不同");

多线程下的单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
多线程时的单例模式
class SingletonSync
{
private static SingletonSync instance;
private static readonly object syncRoot = new object();
private SingletonSync()
{

}
public static SingletonSync GetInstance()
{
lock (syncRoot)
{
if (instance == null)
instance = new SingletonSync();
}
return instance;
}
}

双重锁单例模式

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
//上述做法有点影响性能——对每一个线程都加锁处理…,我们不用让线程每次都先加锁,而只是在实例未创建的时候再加锁处理。同时也保证了多线程安全。提供了双重锁定的方法:
//双重锁定
class SingletonDoubleSync
{
private static SingletonDoubleSync instance;
private static readonly object SyncRoot = new object();
private SingletonDoubleSync()
{

}
public static SingletonDoubleSync GetInstance()
{
if (instance == null)
{
lock (SyncRoot)
{
if (instance == null)
{
instance = new SingletonDoubleSync();
}
}
}
return instance;
}
}

可不可以将内部的instance==null判断删除?

不可以,比如说,有两个instance均为null的线程同时访问单例类,它们都可以通过第一重instance==null的检查,待第一个线程执行完后,第一个线程获取到了实例,若此时没有instance==null的内层判断,第二个线程可以紧接第一个线程后获取到实例。

单例模式总结

客户端不必再考虑是否需要去实例化的问题,而把责任都给了应该负责的类去处理。这涉及了设计模式中的单例模式。实例化与否的过程应该由类自身来判断,而不是别人的责任,别人只是要使用它。

单例模式解决的两个基本问题:全局访问和实例化控制。

单例模式使用的场景

  1. 需要频繁的进行创建和销毁的对象
  2. 创建对象时耗时过多或耗费资源过多(重量级对象)
  3. 经常用到的对象、工具类对象、频繁访问数据库或文件的对象(数据源、session工厂)
  4. 比如说,unity设计游戏中GameManager、AudioManager

如何维护单一的实例?不被外界new?

所有类都有构造方法,不编码系统默认生成空的构造方法,若有显示定义的构造,则默认构造会失效。

只需要将构造方法变成private的,那么外部程序就不能new它了。

那么如何调用这个单一的实例呢?

书写public的方法返回类的实例,类的构造过程在类中实现。

对唯一实例的受控访问

单例模式因为Singleton类封装它的唯一实例,这样它可以严格地控制客户怎么样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。

多线程时候的单例模式

多个线程同时访问Singleton类调用GetInstance方法,会有可能造成产生多个实例。

可以给进程加锁Lock来处理。

Lock是确保当一个线程位于代码段临界区时,另一个线程不进入临界区。如果其他的线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。

也就是说,Lock可以确保前面占用代码的一个线程先跑完,再使下一个要获取该锁的线程开始,否则下一个要跑该段代码段等待。

饿汉式

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
class Singleton {
//私有化构造器
private Singleton() {
}
//内部创建对象实例
private final static Singleton instance = new Singleton();
//对外公有的静态方法
public static Singleton getInstance() {
return instance;
}
}

class Singleton { //静态代码块
//私有化构造器
private Singleton() {}
//内部创建对象实例
private static Singleton instance;
static { // 在静态代码块中,创建单例对象
instance = new Singleton();
}
//对外公有的静态方法
public static Singleton getInstance() {
return instance;
}
}

懒汉式

所谓懒汉式,就是在需要调用的时候再创建类的实例。

线程不安全的懒汉式单例

1
2
3
4
5
6
7
8
9
10
class Singleton { //线程不安全
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() { //调用时才实例化对象,懒汉式
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}

线程安全的懒汉式单例

上面线程不安全,那上锁不就好了,使用synchronized关键字。

这样虽然解决了线程安全,但其实实例化操作只做一次,而获取实例

(即getInstance)的操作是很多次的,把调用的方法加上同步,会大大降低效率。

1
2
3
4
5
6
7
8
9
10
11
class Singleton { //线程安全
private static Singleton instance;
private Singleton() {}
//synchronized同步处理
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}

双重检查多线程安全单例模式

上面代码效率低,那在同步前判断一下有没有实例化不就好了,若没有实例化则用同步方法new一个,否则直接return即可。即所谓的双重检查。 需要用到关键字volatile,防止指令重排。如果不用volatile关键字,就会和线程不安全情形一样,在if判断那会有并发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton { //双重检查
private static volatile Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(instance == null) { //判断是否实例化
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance; //否则直接return
}
}

静态内部类单例模式实现

静态内部类在外部类装载时不会实例化,当调用的时候才会装载并实例化,且JVM保证了其装载时的线程安全性。也能保证懒加载和线程安全,有点像自带版的双重检查。

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
//静态内部类,包含一个静态属性:Singleton
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//对外公有的静态方法,直接返回SingletonInstance.INSTANCE
public static synchronized Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}

枚举实现的单例模式

其实,使用枚举也能实现单例模式,不仅能避免多线程同步问题,也能防止反序列化重新创建新的对象。

1
2
3
4
5
6
enum Singleton {
INSTANCE; //属性
public void say() {
System.out.println("好哎");
}
}