前言
单例模式是常用的一种设计模式,一般用于比如客户端、连接的创建等,防止创建多个导致性能消耗。所以我认为单例模式的核心,就是"防止重复"。本文将在Golang中进行单例模式的实现。
实现
版本1------检测-创建
最基础的版本,就是依照"防止重复"来实现。代码如下:
go
package main
type Test1 struct {
}
var t1 *Test1
func main() {
}
func NewT1() *Test1 {
if t1 == nil {
t1 = &Test1{}
}
return t1
}
可见,只是在创建前,进行了一个判定,如果为空 再创建。不为空则直接返回。
但是这样版本存在有问题------即线程不安全。比如多个goroutine中同时运行其去创建,那么就很容易导致创建重复。
对此,解决方案也很简单------加锁即可。
版本2------加锁-检测-创建
很简单粗暴的加个锁------这样就能保证只有一个去进行检测、创建。规避了问题。
go
var mutex sync.Mutex
func NewT1() *Test1 {
mutex.Lock()
defer mutex.Unlock()
if t1 == nil {
t1 = &Test1{}
}
return t1
}
但是这样带来了新的问题:频繁的加锁、删锁,带来了巨大的性能损耗。诸如t1已经存在的情况,本该直接返回即可,但是却需要白白加锁一次。
版本3------检测-加锁-检测-创建
即所说的Check-Lock-Check
模式。代码如下:
go
func NewT1() *Test1 {
if t1 == nil {
mutex.Lock()
defer mutex.Unlock()
if t1 == nil {
t1 = &Test1{}
}
}
return t1
}
可以看到,就是在最开始的lock之前,进行一次检测。一个if判断的消耗还是很小的,如果存在再进入加锁创建的流程。
在Golang中,可以使用sync/atomic
这个包,原子化的加载一个标志,来实现这套判断。
即:
go
import "sync"
import "sync/atomic"
var initialized uint32
... // 此处省略
func GetInstance() *singleton {
if atomic.LoadUInt32(&initialized) == 1 { // 原子操作
return instance
}
mu.Lock()
defer mu.Unlock()
if initialized == 0 {
instance = &singleton{}
atomic.StoreUint32(&initialized, 1)
}
return instance
}
//此代码直接复制至原文------https://www.liwenzhou.com/posts/Go/singleton/
版本4------Golang常用的方式
饿汉和懒汉式
饿汉
饿汉模式,即像一个饿肚子人一样迫不及待的去享用美食。即 在程序加载的时候就创建并实例化,因此也无需考虑并发等情况。
示例:
go
package main
import "fmt"
type Singleton struct {
// 在这里定义单例对象的属性
}
var instance *Singleton = createInstance()
func createInstance() *Singleton {
// 在这里创建并初始化单例对象
return &Singleton{
// 初始化单例对象的属性
}
}
func GetInstance() *Singleton {
return instance
}
func main() {
// 使用单例模式获取实例
singletonInstance := GetInstance()
// 使用单例实例
fmt.Println(singletonInstance)
}
//此代码复制自原文------https://i6448038.github.io/2023/12/16/singleton/
懒汉
顾名思义,懒得管...等用到时候再创建。此时程序已经启动并正在运行,此时创建实例可能会出现多线程的情况,所以要考虑并发问题。
上述的实现代码便是懒汉模式。
参考资料
https://www.liwenzhou.com/posts/Go/singleton/
https://i6448038.github.io/2023/12/16/singleton/