单例模式(Singleton Design Pattern)
一个类只允许创建一个实例,那这个类就是一个单例类,这种模式就叫单例模式。
从业务上来讲,有些数据在系统中应该只保存一份,如配置初始化、数据库连接初始化等,这时就比较适合单例模式。
单例模式有 饿汉式 和 懒汉式 两种实现。
饿汉式的实现简单,可以在启动时将问题及早暴露,懒汉式虽然支持延迟加载,但是只是把冷启动时间放到了第一次使用的时候,并没有本质上解决问题,并且为了实现懒汉式还不可避免的需要加锁。
饿汉式
- 类加载的时候,实例就创建好了
- 实例创建过程是线程安全的
- 初始化的时间可能会比较长
package singleton
type Singleton struct {}
var singleton *Singleton
func init() {
single = &Singleton{}
}
func GetInstance() *Singleton {
return singleton
}
懒汉式
- 在用到实例的时候去加载
- 实例创建过程中需要加锁
- 延迟加载
- 高并发下会有性能问题
package singleton
import "sync"
type Singleton struct{}
var (
singleton *Singleton
once sync.Once
)
func GetInstance() *Singleton {
once.Do(func(){
singleton = &Singleton{}
})
return singleton
}
这里的实现使用了 sync.Once,它可以保证函数只能被调用一次。sync.Once代码实现也很简单,只有一个uint32字段和一个互斥锁Mutex:
type Once struct {
done uint32
m Mutex
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 0 {
o.doSlow(f)
}
}
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
sync.Once的原理是利用 sync.Mutex 和 atomic 包的原子操作来完成。done 表示当前的操作是否已经被执行:
0表示 Do 操作还没有执行成功
1表示 Do 操作已经执行成功
每次调用 Do 方法都会去检查 done 的值,如果是1就什么都不用做。如果是0就执行 doSlow 流程。doSlow 使用了 sync.Mutex,如果在并发场景下,只会有一个 goroutine 抢到锁执行下去,然后进行二次检查done的值。其它goroutine则会阻塞,等待初始化完成,这样就保证了 sync.Once 只会调用一次。