单例模式
单例模式(单子模式)是常用的软件设计模式,其核心结构中只包括一个被称之为单例的特殊类,通过单例模式可以保证系统中一个类有且仅有一个实例,同时该实例化需要易于外界访问,从而方便对实例个数的控制来节约系统资源。
- 单例对象的类型必须保证只有一个实例存在,全局具有唯一接口访问。
- 单例模式具有两种加载方式分别是懒汉加载和饿汉加载
懒汉加载(Lazy Loading)
懒汉加载通俗来讲,就是创建对象是比较懒,先不急着创建对象,而是在需要加载配置文件时再去创建。
- 懒汉模式是指全局的单例实例只会在第一次被使用时才会被构建
- 懒汉模式的缺点在于非线程安全,由于在创建时可能存在线程访问,因此有可能出现多个实例。
type Singleton struct{}
//私有属性
var singleton *Singleton
//共有方法
func GetInstance() *Singleton {
if singleton == nil {
singleton = &Singleton{}//非线程安全
}
return singleton
}
代码说明
- 构建一个单例结构体
- 设置一个私有变量作为每次需要返回的单例
- 设置一个可以获取单例的共有方法对外暴露
饿汉加载
- 饿汉模式是指全局的单例实例在类装载时才会被构建
- 饿汉模式的缺点在于单例实例初始化可能会比较耗时,因此加载时间会延长。
type Singleton struct{}
var singleton *Singleton = &Singleton{}
func GetInstance() *Singleton {
return singleton
}
饿汉模式会在包加载的时候就会创建单例对象,如果程序中不使用该对象则会持续占用在内存中,虽然和懒汉加载相比更加安全,但会减慢程序启动速度。
懒汉加锁
由于懒汉模式是非线程安全,可通过加锁来解决并发时的线程安全,一般使用互斥锁来解决可能出现的数据不一致问题,但每次加锁也需付出性能代价。
type Singleton struct{}
var (
singleton *Singleton
locker sync.Mutex
)
func GetInstance() *Singleton {
locker.Lock()
defer locker.Unlock()
if singleton == nil {
singleton = &Singleton{}
}
return singleton
}
每次请求单例时都会加锁和解锁,锁的目的在于解决对象初始化时可能出现的并发问题,当对象被创建之后,实际上加锁已经失去了意义,此时会拖慢速度。
使用Golang的sync.Mutex
,其工作模式类似于Linux内核中的futex
对象,初始化时填入的0值将mutext
设置为未锁定状态,同时会保证时间开销最小。
懒汉双重锁(Check-lock-Check)
为解决重复加锁和解锁的问题,可引入双重锁检查机制(Check-lock-Check),又称为DCL(Double Check Lock)。即第一判断时不加锁,第二次判断时加锁以保证线程安全,一旦对象建立则获取对象时就无需加锁了。
为避免懒汉模式每次都需加锁带来的性能损耗,可采用双重锁来避免每次加锁以提高代码效率,即带检查所的单例模式。
type Singleton struct{}
var (
singleton *Singleton
locker sync.Mutex
)
//只有当对象未被初始化时才会加锁和解锁,每次访问都需要检查两遍。
func GetInstance() *Singleton {
if singleton == nil {//单例未实例化才会加锁
locker.Lock()
defer locker.Unlock()
if singleton == nil {//单例未实例化才会创建
singleton = &Singleton{}
}
}
return singleton
}
双重锁的缺陷在于编译器优化不会检查实例的存储状态,同时每次访问都需要检查两次,为解决这个问题,可采用sync/atomic
包中原子性操作来自动加载并设置标记。
import (
"sync"
"sync/atomic"
)
type Singleton struct{}
var (
instance *Singleton
initialized uint32
locker sync.Mutex
)
func GetInstance() *Singleton {
if atomic.LoadUint32(&initialized) == 1 {//一次判断即可返回
return instance
}
locker.Lock()
defer locker.Unlock()
if initialized == 0 {
instance = &Singleton{}
atomic.StoreUint32(&initialized, 1)//原子装载
}
return instance
}
优雅实现
sync.Once
提供的Do(f func())
方法会使用添加锁来进行原子操作来保证回调函数只执行一次,sync.Once
内部本质上也是双重检查的方式。
type Singleton struct{}
var (
singleton *Singleton
once sync.Once
)
func GetInstance() *Singleton {
once.Do(func() {
singleton = &Singleton{}
})
return singleton
}