GO 单例模式

 

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

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

go并非一种面向对象的语言,因此咱们使用结构体来替代golang

有几种方式:设计模式

  • 懒汉模式安全

  • 饿汉模式服务器

  • 双重检查锁机制并发

下面拆分讲解:函数

懒汉模式

  1. 构建一个示例结构体
   type example struct {
   	name string
   }
  1. 设置一个私有变量做为每次要返回的单例
  var instance *example
  1. 写一个能够获取单例的方法
    func GetExample() *example {
    
    	// 存在线程安全问题,高并发时有可能建立多个对象
    	if instance == nil {
    		instance = new(example)
    	}
    	return instance
    }
  1. 测试一下高并发

      func main() {
      	s := GetExample()
      	s.name = "第一次赋值单例模式"
      	fmt.Println(s.name)
      
      	s2 := GetExample()
      	fmt.Println(s2.name)
      }

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

饿汉模式

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

  // 构建一个结构体,用来实例化单例
  type example2 struct {
  	name string
  }
  
  // 声明一个私有变量,做为单例
  var instance2 *example2
  
  // init函数将在包初始化时执行,实例化单例
  func init() {
  	instance2 = new(example2)
  	instance2.name = "初始化单例模式"
  }
  
  func GetInstance2() *example2 {
  	return instance2
  }
  
  func main() {
  	s := GetInstance2()
  	fmt.Println(s.name)
  }

 

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

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

双重检查机制

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

因此修改上面的GetInstance() 方法以下:

   var mux Sync.Mutex
   func GetInstance() *example {
       mux.Lock()                    
       defer mux.Unlock()
       if instance == nil {
           instance = &example{}
       }
      return instance
   }

 

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

  func GetInstance() *example {
      if instance == nil {  // 单例没被实例化,才会加锁 
          mux.Lock()
          defer mux.Unlock()
          if instance == nil {  // 单例没被实例化才会建立
  	            instance = &example{}
          }
      }
      return instance
  }

 

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

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

   import "sync"  
   import "sync/atomic"
   
   var initialized uint32
   
   func GetInstance() *example {
   	
   	  // 一次判断便可返回
      if atomic.LoadUInt32(&initialized) == 1 {
   		return instance
   	   }
       mux.Lock()
       defer mux.Unlock()
       if initialized == 0 {
            instance = &example{}
            atomic.StoreUint32(&initialized, 1) // 原子装载
   	}
   	return instance
   }

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

sync.OnceDo方法能够实如今程序运行过程当中只运行一次其中的回调,因此最终简化的代码以下:

 type example3 struct {
 	name string
 }
 
 var instance3 *example3
 var once sync.Once
 
 func GetInstance3() *example3 {
 
 	once.Do(func() {
 		instance3 = new(example3)
 		instance3.name = "第一次赋值单例"
 	})
 	return instance3
 }
 
 func main() {
 	e1 := GetInstance3()
 	fmt.Println(e1.name)
 
 	e2 := GetInstance3()
 	fmt.Println(e2.name)
 }
相关文章
相关标签/搜索