• 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成员变量

    • 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

  • 相关阅读:
    超详细的sql2005图解安装全过程【图文】SQL Server 2005 安装图解(图文详解+全程截图)
    C# WinForm控件的拖动和缩放的实现
    C#中使用组合键事件,c#2005中组合键的应用
    C#判断Shift,Alt,Ctrl是否被按下,确定所按下的组合键
    详解破解VS2010正式版的方法(仅供技术交流)
    C# listview中显示imagelist中的图片
    序列化
    收集一些常用的正则表达式【转载】
    iframe+ajaxSubmit结合续篇(判断图片(文件)大小,上传图片,验证码刷新)
    完美去掉a标签和按钮加背景图片阴影
  • 原文地址:https://www.cnblogs.com/youxin/p/16204993.html
Copyright © 2020-2023  润新知