定义
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式,可以看到定义简单,算是设计模式中最简单的一个模式了。
饿汉模式
即还未使用该对象时,对象已经创建完成。
方法是通过golang 的init函数,在导包时就自动执行。
package mian import "fmt" var instanse *singler type singler struct { Name string } func NewSingler()*singler{ return instanse } func init() { instanse = new(singler) instanse.Name = "test" } func main() { singler := NewSingler() fmt.Println(singler.Name) }
懒汉模式
即用户需要的时候再创建,这里采用锁的模式去创建,为了减少锁的判断的次数,可以采用双重判断机制。在对象创建完成后,后续不需要加锁处理 。
package main import ( "fmt" "sync" ) var instanse *singler var mutex sync.Mutex type singler struct { Name string } func NewSingler()*singler { if instanse == nil{ mutex.Lock() defer mutex.Unlock() if instanse == nil{ instanse = new(singler) instanse.Name = "test" } } return instanse } func main() { singler := NewSingler() fmt.Println(singler.Name) }
必须要DCL
双重检查加锁单例模式为什么两次if判断?
java 单例模式中双重检查锁定 volatile 的作用?
[推荐阅读]java 单例模式中双重检查锁定 volatile 的作用?
参考URL: https://www.zhihu.com/question/56606703?sort=created
volatile 是保证了可见性还是有序性?
主要是禁止重排序,初始化一个实例(SomeType st = new SomeType())在java字节码中会有4个步骤,
申请内存空间,
初始化默认值(区别于构造器方法的初始化),
执行构造器方法
连接引用和实例。
这4个步骤后两个有可能会重排序,1234 1243都有可能,造成未初始化完全的对象发布。
为什么要禁止重排序?
确保先执行构造器方法,再将引用和实例连接到一起。如果没有禁止重排序,会导致另一个线程可能获取到尚未构造完成的对象。
为什么没有起到可见性的作用?
JSR-133
An unlock on a monitor happens before every subsequent lock on that same monitor
第二次非null判断是在加锁以后,则根据这一条,另一个线程一定能看到这个引用被赋值。所以即使没有volatile,依旧能保证可见性。
总结: DCL单例中 1、synchronized已经保证了可见性,不需要volatile来保证。2、volatile的作用是禁止重排序。 因此,主要是为了保证原子操作。
————————————————
原文链接:https://blog.csdn.net/inthat/article/details/107807343
https://blog.csdn.net/m0_37681974/article/details/108139809
懒汉模式之sync.Once
在上面的代码中,是我们自己实现的防止创建多个的情况,在golang的包中有个很好用的工具sync.Once,它可以保证只执行一次。代码如下:
sync.Once.Do(f func())是一个挺有趣的东西,能保证once只执行一次,无论你是否更换once.Do(xx)这里的方法,这个sync.Once块只会执行一次。
package main import ( "fmt" "sync" ) var once sync.Once = sync.Once{} var instanse *singler type singler struct { Name string } func NewSingler() *singler { once.Do(func() { instanse = new(singler) instanse.Name = "这是一个单例" }) return instanse } func main() { singler := NewSingler() fmt.Println(singler.Name) }
package main import ( "fmt" "sync" ) var once sync.Once var con string func main() { once.Do(func() { con = "hello Test once.Do" }) fmt.Println(con) }
之前提到过 Go 的并发辅助对象:WaitGroup。同样的, sync.Once 也是 Go 官方的一并发辅助对象,它能够让函数方法只执行一次,达到类似 init 函数的效果。我们来看看它的简单用法:
func main() {
var once sync.Once
onceFunc := func() {
fmt.Println("Only once")
}
for i := 0; i < 10; i++ {
once.Do(onceFunc)
}
}
这里执行后我们将只看到一次 Only once 的打印信息,这就是 sync.Once 的一次性效果
源码分析
接下来分析 sync.Do 究竟是如何实现的,它存储在包sync下 once.go 文件中,源代码如下:
// sync/once.go type Once struct { done uint32 // 初始值为0表示还未执行过,1表示已经执行过 m Mutex } func (o *Once) Do(f func()) { // 判断done是否为0,若为0,表示未执行过,调用doSlow()方法初始化 if atomic.LoadUint32(&o.done) == 0 { // Outlined slow-path to allow inlining of the fast-path. o.doSlow(f) } } // 加载资源 func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() // 采用双重检测机制 加锁判断done是否为零 if o.done == 0 { // 执行完f()函数后,将done值设置为1 defer atomic.StoreUint32(&o.done, 1) // 执行传入的f()函数 f() } }
,第一部分为 Once 的结构体组成结构,第二部分为 Do 函数的实现原理,我会在代码上加上注释,保证用心阅读完都有收获。
结构体
type Once struct {
done uint32 // 初始值为0表示还未执行过,1表示已经执行过
m Mutex
}
首先定义一个struct结构体 Once ,里面存储两个成员变量,分别为 done 和 m 。
done成员变量
- 1表示资源未初始化,需要进一步初始化
- 0表示资源已初始化,无需初始化,直接返回即可
m成员变量
- 为了防止多个goroutine调用 doSlow() 初始化资源时,造成资源多次初始化,因此采用 Mutex 锁机制来保证有且仅初始化一次
Do
func (o *Once) Do(f func()) {
// 判断done是否为0,若为0,表示未执行过,调用doSlow()方法初始化
if atomic.LoadUint32(&o.done) == 0 {
// Outlined slow-path to allow inlining of the fast-path.
o.doSlow(f)
}
}
// 加载资源
func (o *Once) doSlow(f func()) {
o.m.Lock()
defer o.m.Unlock()
// 采用双重检测机制 加锁判断done是否为零
if o.done == 0 {
// 执行完f()函数后,将done值设置为1
defer atomic.StoreUint32(&o.done, 1)
// 执行传入的f()函数
f()
}
调用 Do 函数时,首先判断done值是否为0,若为1,表示传入的匿名函数 f() 已执行过,无需再次执行;若为0,表示传入的匿名函数 f() 还未执行过,则调用 doSlow() 函数进行初始化。
在 doSlow() 函数中,若并发的goroutine进入该函数中,为了保证仅有一个goroutine执行 f() 匿名函数。为此,需要加互斥锁保证只有一个goroutine进行初始化,同时采用了双检查的机制(double-checking),再次判断 o.done 是否为 0,如果为 0,则是第一次执行,执行完毕后,就将 o.done 设置为 1,然后释放锁。
即使此时有多个 goroutine 同时进入了 doSlow 方法,因为双检查的机制,后续的 goroutine 会看到 o.done 的值为 1,也不会再次执行 f。
这样既保证了并发的 goroutine 会等待 f 完成,而且还不会多次执行 f。
参考:https://www.jianshu.com/p/c8190ca4b3bd