一个类只允许创建一个对象或实例,而且自行实例化并向整个系统提供该实例,这个类就是一个单例类,它提供全局访问的方法。这种设计模式叫单例设计模式,简称单例模式。
单例模式的要点:
从业务概念上来看,有些数据在系统中只应该保留一份,就比较适合设计为单例类。比如,系统的配置信息。
使用场景:
优点:
缺点:
student
对象的语句,进程就会在地址空间里创建一个student
对象。如果在一个进程中创建另一个进程,操作系统会给新进程分配新的地址空间,而且将旧地址空间的内容拷贝一份,包括代码和数据,这样新进程里有且只有一个student
对象,旧进程里也有且只有一个student
,但是这两个对象并不是同一个对象。golang
里主要使用协程,而且协程的id并不会暴露出来。private
访问权限getInstance
的性能问题,比如是否有加锁等在类加载的时候实例就已经创建好了,实例的创建过程线程安全,不支持延迟加载
有两种实现方式,第一种是定义全局变量的时候创建实例,第二种是采用包的init
函数创建实例
注意这里的Singleton
类型也要是大写字母开头的,因为GetInstance
方法是大写字母开头,表明包外可访问,那么该方法的返回值也需要包外可访问。
package singleton
// 单例模式 饿汉式实现
type Singleton struct{}
var singleton *Singleton
//1.全局变量的实现方式
//var singleton1 = &Singleton{}
//2. 包的init函数实现方式
func init() {
singleton = &Singleton{}
}
func GetInstance() *Singleton {
return singleton
}
在获取实例的时候再去创建,实例创建过程需要加锁,支持延迟加载,不支持高并发
只是对懒汉式创建的一个理解,在GetInstnce
方法里判断singleton
是否为空,为空的话就去创建一个实例,否则直接返回该实例。
存在线程安全问题,高并发的时候会创建多个对象,不推荐使用。
package singleton
// 单例模式 懒汉式实现 不加锁
type Singleton struct{}
var singleton *Singleton
func GetInstance() *Singleton {
if singleton == nil {
singleton = &Singleton{}
}
return singleton
}
对GetInstance
整个方法进行加锁,确保并发安全,但是每一个对象创建的时候都需要进行加锁解锁,效率低
package singleton
import "sync"
// 单例模式 懒汉式实现 方法锁
type Singleton struct{}
var singleton *Singleton
var mu sync.Mutex
func GetInstance() *Singleton {
mu.Lock()
defer mu.Unlock()
if singleton == nil {
singleton = &Singleton{}
}
return singleton
}
在懒汉式的基础上,将方法的锁改为类级别的锁,相对于懒汉式的粒度更小,不用每次都去获取锁
借助sync.Once
确保只创建一次
package singleton
import "sync"
// 单例模式 懒汉式实现 双重检测
type LazySingleton struct{}
var lazySingleton *LazySingleton
var once = &sync.Once{}
func GetLazyInstance() *LazySingleton {
if lazySingleton == nil {
once.Do(func() {
lazySingleton = &LazySingleton{}
})
}
return lazySingleton
}
java
的静态内部类,线程安全,延迟加载
java
的枚举特性
测试饿汉式的init
实现方法和懒汉式的sync.Once
实现方法
package singleton
import "testing"
func BenchmarkGetInstanceParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if GetInstance() != GetInstance() {
b.Errorf("test fail")
}
}
})
}
func BenchmarkGetLazyInstanceParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if GetLazyInstance() != GetLazyInstance() {
b.Errorf("test fail")
}
}
})
}
在对应的目录下执行:
go test -bench='Parallel$' -benchmem .
可以看出饿汉式的性能更好一点
参考链接
图解设计模式
Go设计模式
Golang单例模式