一、单例模式

51 阅读2分钟

当需要某个对象只需要一个实例的时候,我们就可以使用单例模式。

在包中,只暴露出一个函数供外界进行访问该对象。

单例模式的优点是整个程序的生命周期中之存在一个对象,避免频繁创建和销毁,节约了系统资源

利用场景:

  • 作用于全局的计数器对象就可以使用单例模式保证只有一个
  • 只需要一个作用于全局的日志记录器,可以使用单例模式进行创建,确保了全局唯一性

根据实例在什么时候被创建出来,我们可以分为两类:

  • 一是在程序启动的时候就初始化创建
  • 二是在需要使用这个对象的时候才进行初始化创建,但是只在第一次访问的时候被创建

饿汉模式

在这个模式中,对象需要在程序启动的时候就被创建,我们可以使用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
}