vlambda博客
学习文章列表

golang设计模式之单例模式


单例模式

wiki百科: 单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

单例模式要实现的效果就是,对于应用单例模式的类,整个程序中只存在一个实例化对象

go并不是一种面向对象的语言,所以我们使用结构体来替代

有几种方式:

  • 懒汉模式

  • 饿汉模式

  • 双重检查锁机制

下面拆分讲解:

懒汉模式

  1. 构建一个示例结构体


  
    
    
  
  1. type example struct {

  2. name string

  3. }


  1. 设置一个私有变量作为每次要返回的单例


  
    
    
  
  1. var instance *example

  2. 复制代码

  1. 写一个可以获取单例的方法


  
    
    
  
  1. funcGetExample() *example {


  2. // 存在线程安全问题,高并发时有可能创建多个对象

  3. if instance == nil {

  4. instance = new(example)

  5. }

  6. return instance

  7. }



  1. 测试一下

    1. func main() {

    2.   s := GetExample()

    3.   s.name = "第一次赋值单例模式"

    4.   fmt.Println(s.name)


    5.   s2 := GetExample()

    6.   fmt.Println(s2.name)

    7. }


懒汉模式存在线程安全问题,在第3步的时候,如果有多个线程同时调用了这个方法, 那么都会检测到instancenil,就会创建多个对象,所以出现了饿汉模式...

饿汉模式

与懒汉模式类似,不再多说,直接上代码


  
    
    
  

  1. // 构建一个结构体,用来实例化单例

  2. type example2 struct {

  3. name string

  4. }


  5. // 声明一个私有变量,作为单例

  6. var instance2 *example2


  7. // init函数将在包初始化时执行,实例化单例

  8. funcinit() {

  9. instance2 = new(example2)

  10. instance2.name = "初始化单例模式"

  11. }


  12. funcGetInstance2() *example2 {

  13. return instance2

  14. }


  15. funcmain() {

  16. s := GetInstance2()

  17. fmt.Println(s.name)

  18. }



饿汉模式将在包加载的时候就创建单例对象,当程序中用不到该对象时,浪费了一部分空间

和懒汉模式相比,更安全,但是会减慢程序启动速度

双重检查机制

懒汉模式存在线程安全问题,一般我们使用互斥锁来解决有可能出现的数据不一致问题

所以修改上面的GetInstance() 方法如下:


  
    
    
  
  1. var mux Sync.Mutex

  2. funcGetInstance() *example {

  3. mux.Lock()

  4. defer mux.Unlock()

  5. if instance == nil {

  6. instance = &example{}

  7. }

  8. return instance

  9. }


如果这样去做,每一次请求单例的时候,都会加锁和减锁,而锁的用处只在于解决对象初始化的时候可能出现的并发问题 当对象被创建之后,加锁就失去了意义,会拖慢速度,所以我们就引入了双重检查机制(Check-lock-Check), 也叫DCL(Double Check Lock), 代码如下:


  
    
    
  
  1. funcGetInstance() *example {

  2. if instance == nil { // 单例没被实例化,才会加锁

  3. mux.Lock()

  4. defer mux.Unlock()

  5. if instance == nil { // 单例没被实例化才会创建

  6. instance = &example{}

  7. }

  8. }

  9. return instance

  10. }


这样只有当对象未初始化的时候,才会又加锁和减锁的操作

但是又出现了另一个问题:每一次访问都要检查两次,为了解决这个问题,我们可以使用golang标准包中的方法进行原子性操作:


  
    
    
  
  1. import "sync"

  2. import "sync/atomic"


  3. var initialized uint32


  4. funcGetInstance() *example {

  5. // 一次判断即可返回

  6. if atomic.LoadUInt32(&initialized) == 1 {

  7. return instance

  8. }

  9. mux.Lock()

  10. defer mux.Unlock()

  11. if initialized == 0 {

  12. instance = &example{}

  13. atomic.StoreUint32(&initialized, 1) // 原子装载

  14. }

  15. return instance

  16. }


以上代码只需要经过一次判断即可返回单例,但是golang标准包中其实给我们提供了相关的方法:

sync.OnceDo方法可以实现在程序运行过程中只运行一次其中的回调,所以最终简化的代码如下:


  
    
    
  

  1. type example3 struct {

  2. name string

  3. }


  4. var instance3 *example3

  5. var once sync.Once


  6. funcGetInstance3() *example3 {


  7. once.Do(func() {

  8. instance3 = new(example3)

  9. instance3.name = "第一次赋值单例"

  10. })

  11. return instance3

  12. }


  13. funcmain() {

  14. e1 := GetInstance3()

  15. fmt.Println(e1.name)


  16. e2 := GetInstance3()

  17. fmt.Println(e2.name)

  18. }


文章来源网络,如有侵权请联系小编

喜欢的可以加Q群162542073一起讨论,交流