单例模式(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 只会调用一次。

results matching ""

    No results matching ""