当需要某个对象只需要一个实例的时候,我们就可以使用单例模式。
在包中,只暴露出一个函数供外界进行访问该对象。
单例模式的优点是整个程序的生命周期中之存在一个对象,避免频繁创建和销毁,节约了系统资源
利用场景:
- 作用于全局的计数器对象就可以使用单例模式保证只有一个
- 只需要一个作用于全局的日志记录器,可以使用单例模式进行创建,确保了全局唯一性
根据实例在什么时候被创建出来,我们可以分为两类:
- 一是在程序启动的时候就初始化创建
- 二是在需要使用这个对象的时候才进行初始化创建,但是只在第一次访问的时候被创建
饿汉模式
在这个模式中,对象需要在程序启动的时候就被创建,我们可以使用Go语言的init函数进行创建:
var hungry *Hungry
func init() {
hungry = &Hungry{}
}
func GetHungry() *Hungry {
return hungry
}
GetHungry函数是提供给外界获取对象的函数。我们使用该对象的时候只需要每次调用该函数即可,我们通过指针的形式保证了每一次返回的值都是指向该对象的指针。
懒汉模式
懒汉模式只在第一次使用这个对象的时候才进行创建:
type Lazy struct{}
var lazy *Lazy
// GetLazy 非协程安全
func GetLazy() *Lazy {
if lazy == nil {
lazy = &Lazy{}
}
return lazy
}
GetLazy函数中,我们会判断对象是否被创建,如果没有被创建,那么我们创建一个对象;如果对象已经被创建了,那么我们就可以直接获取该对象。
但是这种写法在并发情况下并不是安全的,该对象可能会被多次创建,因此需要上锁。
type Lazy struct{}
var (
lazy *Lazy
mu sync.Mutex
)
// GetLazy 协程安全
func GetLazy() *Lazy {
if lazy == nil {
mu.Lock()
defer mu.Unlock()
if lazy == nil{
lazy = &Lazy{}
}
}
return lazy
}
在这里使用了一个双重检查机制,减少了使用锁的频率。
在Go中,有 sync.Once 可以实现在程序生命周期内只执行一次。因此,更简便的写法为:
type Lazy struct{}
var lazy *Lazy
var once sync.Once
// GetLazy 使用go自带的once原子性操作
func GetLazy() *Lazy {
once.Do(
func() {
lazy = &Lazy{}
})
return lazy
}