单例模式
饿汉式
这种方式的单例对象在包加载的时候就会自动初始化instance实例,所以被称为饿汉式。
特点: 在包被导入的时候会自动初始化instance实例。
优点:
- 实现简单。
- 在程序启动时就已经创建好了单例对象,无需关心并发问题,线程安全。
缺点:
- 如果单例对象占用资源较多,而在程序运行过程中其实并没有使用,就会造成资源的浪费。
- 如果单例实例化时初始化内容过多,会造成程序加载用时较长。
注意: 尽管饿汉式实现单例模式的方式简单,但是大多数情况下不推荐,因为如果单例实例化时初始化内容过多,会造成程序加载用时较长。
package singleton
type Singleton struct{}
var instance = &Singleton{}
func GetInstance() *Singleton {
return instance
}
懒汉式(Lazy Loading)
特点: 将实例化Singleton结构体的代码部分移到函数内部,这样将结构体实例化的步骤延迟到了GetInstance第一次被调用的时候。
优点:
- 单例对象在需要的时候才创建,节约资源。
缺点:
- 在多线程环境下不保证单例的唯一性,可能会创建多个单例对象。
注意: 这种方法通过instance==nil判断来实现单例并不是十分可靠,因为如果有多个goroutine调用GetInstance()就无法保证并发安全,可能会创建多个实例。
package singleton
type Singleton struct{}
var instance *Singleton
func GetInstance() *Singleton {
if instance == nil {
instance = &Singleton{}
}
return instance
}
懒汉式(Lazy Loading)–线程安全
特点: 支持并发,线程安全。
优点:
- 单例对象在需要的时候才创建,节约资源。
- 在多线程环境下,能保证单例的唯一性。
缺点:
- 这种写法虽然线程安全,但是每次调用该方法时都需要进行锁操作,在性能上相对不高效。
package singleton
import "sync"
type Singleton struct{}
var instance *Singleton
var mu sync.Mutex
func GetInstance() *Singleton {
mu.Lock()
defer mu.Unlock()
if instance == nil {
instance = &Singleton{}
}
return instance
}
双重锁定
特点: 在懒汉式-线程安全的代码上加了一层判断,判断instance是否为nil,通过这种形式来兼顾安全和性能
优点:
- 单例对象在需要的时候才创建,节约资源。
- 在多线程环境下,能保证单例的唯一性。
- 只有在第一次创建单例对象时才需要获取锁,提高了性能。
缺点:
- 实现复杂。
解释:
- 最外层的instance判断是为了提高程序的执行效率,避免每次调用GetInstance都去上锁,将加锁的粒度更加精细化,
- 内层的instance判断则考虑了并发安全,即使在极端情况下多个goroutine走到加锁这一步,内层的判断也会生效
package singleton
import "sync"
type Singleton struct{}
var instance *Singleton
var mu sync.Mutex
func GetInstance() *Singleton {
if instance == nil {
mu.Lock()
defer mu.Unlock()
if instance == nil {
instance = &Singleton{}
}
}
return instance
}
Gopher 惯用方案
通过sync.Once 来确保创建对象的方法只执行一次,sync.Once内部本质上也是双重检查的方式,但在写法上会比自己写双重检查更简洁
优点:
- 单例对象在需要的时候才创建,节约资源。
- 在多线程环境下,能保证单例的唯一性。
缺点:
- 相比于其他方法,没有明显的缺点
package singleton
import "sync"
type Singleton struct{}
var instance *Singleton
var once sync.Once
func GetInstance() *Singleton {
once.Do(func () {
instance = &Singleton{}
})
return instance
}