什么是单例模式
一个类只允许创建一个对象(或者实例)
为什么要使用单例
- 处理资源的访问冲突
- 表示全局唯一类
- …
如何实现一个单例
- 构造函数需要是
private
访问权限的,这样才能避免外部通过new
创建实例 - 考虑对象创建时的线程安全问题
- 考虑是否支持延迟加载
- 考虑
getInstance()
性能是否高(是否加锁)
饿汉式
在类加载的时候,静态实例就已经创建并初始化好了,所以是线程安全的。
type singleton1 struct{}
var instance1 *singleton1
func GetInstance1() *singleton1 {
return instance1
}
func init() {
instance1 = &singleton1{}
}
懒汉式(带锁)
支持延迟加载,当实例创建后,加锁是非必要的,故有性能瓶颈。
type singleton2 struct{}
var instance2 *singleton2
var mu2 sync.Mutex
func GetInstance2() *singleton2 {
mu2.Lock()
defer mu2.Unlock()
if instance2 == nil {
instance2 = &singleton2{}
}
return instance2
}
双重检测
type singleton3 struct{}
var instance3 *singleton3
var mu3 sync.Mutex
func GetInstance3() *singleton3 {
if instance3 == nil { // <-- Not yet perfect. since it's not fully atomic
mu3.Lock()
defer mu3.Unlock()
if instance3 == nil {
instance3 = &singleton3{}
}
}
return instance3
}
Go惯用方式(sync.Once)
type singleton struct{}
var instance *singleton
var once sync.Once
func GetInstance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}
扩展
-
如何实现线程内唯一的单例?
构造一个key为线程ID,value为单例对象的Map,如果存在就返回,否则创建并插入到Map。
-
如何实现集群内的单例?
需要把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。