简介
单例模式在面向对象中是一个常见、简单的模式。
英文名称:Singleton Pattern
该模式规定一个类只允许有一个实例,而且自行实例化并向整个系统提供这个实例。因此该模式的要点有:
- 只有一个实例
- 必须自行创建
- 必须自行向整个系统提供这个实例
意图
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决
避免一个全局使用的类频繁地创建与销毁。
何时使用
当您想控制实例数目,节省系统资源的时候;或者系统不允许存在多实例的时候。
如何实现
判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
优缺点
- 优点
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用(比如写文件操作)。
- 缺点
- 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
饿汉式和懒汉式单例
根据实例化的时机,单例模式一般分成饿汉式和懒汉式。
- 饿汉式:在定义实例时直接实例化,
var Instance = new(singleton) - 懒汉式:在实例化方法中进行实例化,
那两者有什么区别或优缺点?
- 饿汉式单例类:在自己被加载时就将自己实例化。即便加载器是静态的,饿汉式单例类被加载时仍会将自己实例化。单从资源利用率角度讲,这个比懒汉式单例类稍差些。从速度和反应时间角度讲,则比懒汉式单例类稍好些。
- 懒汉式单例类:在实例化时,必须处理好在多个线程同时首次引用此类时的访问限制问题,特别是当单例类作为资源控制器在实例化时必须涉及资源初始化,而资源初始化很有可能耗费时间。这意味着出现多线程同时首次引用此类的几率变得较大。
代码实现
饿汉式
// 饿汉式单例模式package singletontype singleton struct {count int}var Instance = new(singleton)func (s *singleton) Add() int {s.count++return s.count}// 使用,在其他包中可以直接调用 Instance 的 Add 方法c := singleton.Instance.Add()
- 有且只提供一个实例变量
Instance - 自行创建
Instance是个全局的、面向整个系统的实例
懒汉式
// 懒汉式单例模式package singletonimport "sync"type singleton struct {count int}var (instance *singletonmutex sync.Mutex)func (s *singleton) Add() int {s.count++return s.count}// 普通实例化方法func New1() *singleton {mutex.Lock()defer mutex.Unlock()if instance == nil {instance = new(singleton)}return instance}// 双重检查实现func New2() *singleton {if instance == nil { // 第一次检查(①)// 这里可能有多于一个 goroutine 同时达到(②)mutex.Lock()// 这里每个时刻只会有一个 goroutine(③)if instance == nil { // 第二次检查(④)instance = new(singleton)}mutex.Unlock()}return instance}// sync.Once 实现var once sync.Oncefunc New3() *singleton {once.Do(func() {instance = new(singleton)})return instance}
- 包级变量变成非导出(instance),注意这里类型应该用指针,因为结构体的默认值不是 nil
- 提供了实例化函数,按照 Go 的惯例,命名为 New()
- 多 goroutine 保护,Go 使用 sync.Mutex
此外,Go语言中还有一些其他“黑魔法”,比如利用 init 函数来初始化唯一的单例。不过一般都不太建议,还是常规方式来。
使用场景
在 Go 语言中,一般推荐优先考虑使用饿汉式。但如果初始化比较耗时,懒汉式延迟初始化是更好的选择。
在 Go 语言中,如下两个场景比较适合使用单例模式:
- 数据库实例:只想创建一个 DB 对象实例,该实例在整个应用程序中使用。
- 日志实例:同样,只创建一个 Logger 的实例,并且在整个应用程序中使用它。
