定义

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

单例模式的要点有三个:

  • 某个类只能有一个实例;
  • 这个类必须自行创建这个实例;
  • 这个类必须自行向整个系统提供这个实例。

单例模式又名单件模式或者单态模式。

图解

20200322160256

我应该何时用它?

判断是否使用单例模式,你需要确定:

  • 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
  • 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

例子

饿汉式——静态初始化

创建流程:

  1. 创建静态的私有变量,并实例化(在第一次引用类的任何成员时创建实例)。
  2. 私有化构造函数(不能外部new出来)。
  3. 创建全局的唯一访问点。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Singleton
{
private static Singleton _instance = new Singleton();
private Singleton(){}
public static Singleton Instance
{
get
{
return _instance;
}
}

public void DoSomethingFunction(){}
}

// 使用

public class Client
{
Singleton.Instance.DoSomethingFunction();
}

懒汉式——多线程中的双重锁定

懒汉式单例是在第一次使用时创建,可以延时加载,等待用户调用静态方法时才创建对象。

一个普通的懒汉式单例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton
{
private static Singleton _instance;
// 构造函数私有,不能被外部直接调用
private Singleton(){}
// 通过属性提供实例
public static Singleton Instance
{
get
{
if(_instance == null)
{
_instance = new Singleton();
}
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
public class Singleton
{
private static Singleton _instance;
// 🔒
private static readonly object syn = new object();
// 构造函数私有,不能被外部直接调用
private Singleton(){}
// 通过属性提供实例
public static Singleton Instance
{
get
{
if(_instance == null)
{
lock(syn) // 加锁,防止多线程访问
{
if(_instance == null)
{
_instance = new Singleton();
}
}
}
return _instance;
}
}
}

这个还是有缺点的,虽然对方法进行了同步,解决了多线程的访问不安全性,但是每次访问Instance属性都会去判断锁的状态,比较耗时。

因此我们还有内部类的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton
{
// 构造函数私有,不能被外部直接调用
private Singleton(){}
// 通过属性提供实例
public static Singleton Instance
{
get
{
return SingletonHolder._instance;
}
}
// 在静态内部类中,实现延时加载
private static class SingletonHolder
{
public static Singleton _instance = new Singleton();
}
}

这样既保证了线程安全,又能够延迟加载。

具体为什么在内部类中进行实例化就能够保证延迟加载且线程安全呢?可以看这篇文章这篇文章

相关资料还可以看这篇文章

两种模式的比较

饿汉式单例类在类被加载时就将自己实例化,特点在于:

  • 无需考虑多线程访问问题,可以确保实例的唯一性。
  • 从调用速度和反应时间角度来讲,饿汉式优于懒汉式。
  • 从资源利用的角度来讲,不管系统运行时是否使用该单例对象,饿汉式都要创建,这一点不如懒汉式。

而懒汉式单例在第一次使用时创建,无须一直占用系统资源,特点在于:

  • 实现了延迟加载。
  • 必须处理好多个线程同时访问的问题,特别作为资源管理器的时候。
  • 需要通过多重检查锁定机制控制,导致系统性能受到一定影响。

不要过度使用!

想要用好单例,最重要的是想怎么样用少单例,越少越好。

单例是通常是全生命周期永不销毁的,所以承担的也是全局功能。如果你做的不是全局功能,你本来就不应该用单例。局部功能必定有入口和出口以及自己的生命周期,你就应该把需要的东西放自己身上,随自己销毁,而不是单例出去。

不得不用单例的场景

  • 各种辅助函数,全局的不带状态信息的工具类等。
  • 配表数据,数据直接转成代码或者静态数据,没有并发问题。

使用单例更方便的场景

  • 类似于AssetBundle管理器(资源管理器)、音乐音效管理器这种,属于一定在周期内调用,且不影响Gameplay的情景。
  • 全局状态管理,管理游戏整体流程的。
  • 类似于Debug这个类,Uniy帮你进行了制作,直接用很方便。

不建议用单例的场景

  • 游戏内支持一些比较特殊的功能时,例如重放(说的就是你命令模式)、热更新、断线重连等。这个情况相当于通过一个逻辑线程来驱动整个游戏逻辑。
  • 单例之间总会产生依赖,这会导致你需要人肉维护一坨坨代码。新增加单例后顺序可能会乱掉,逻辑会爆掉。

在Unity中实现的泛型单例模板

在Unity中,很多游戏管理类都是唯一的,用一个GameObject游戏物体运行就行了,那么我们这里有一个单例模板,哪个类需要用单例类,去继承这个单例类就行了:

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
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
private static T _instance;
private static object _lock = new object();
public static T Instance
{
get
{
if (applicationIsQuitting) return null;
      lock(_lock)
{
if (_instance == null)
{
_instance = (T) FindObjectOfType(typeof(T));
if ( FindObjectsOfType(typeof(T)).Length > 1 ) return _instance;
if (_instance == null)
{
GameObject singleton = new GameObject();
_instance = singleton.AddComponent<T>();
singleton.name = "(Singleton) "typeof(T).ToString();
DontDestroyOnLoad(singleton);
}
}
return _instance;
}
}
}
private static bool applicationIsQuitting = false;
public void OnDestroy ()
{
applicationIsQuitting = true;
}
}

这个类实现了多线程锁机制,能够保证数据安全。如果场景中没有该类,就会自动创建一个“(Singleton)类名”的游戏物体,一旦创建,切换场景也不会销毁。

这一段泛型模板摘自unity3D – 单例泛型模板

另一版也可以使用:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using UnityEngine;
public abstract class Singleton<T> where T : new()
{
private static T _instance;
static object _lock = new object();
public static T Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
_instance = new T();
}
}
return _instance;
}
}
}
// 通过Unity生命周期
public class MonoSingleton<T> : MonoBehaviour where T : Component
{
private static T _instance;
public static T Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType(typeof(T)) as T;
if (_instance == null)
{
GameObject obj = new GameObject();
obj.hideFlags = HideFlags.HideAndDontSave;
_instance = (T)obj.AddComponent(typeof(T));
}
}
return _instance;
}
}

public virtual void Awake()
{
DontDestroyOnLoad(this.gameObject);
if (_instance == null)
{
_instance = this as T;
}
else
{
Destroy(gameObject); ;
}
}
}