其他分享
首页 > 其他分享> > golang单例模式

golang单例模式

作者:互联网

定义

单例(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 ,里面存储两个成员变量,分别为 donem

done成员变量

m成员变量

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

 

标签:instanse,单例,singler,sync,模式,golang,done,func,Once
来源: https://www.cnblogs.com/youxin/p/16204993.html