内容简介:wiki百科: 单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。单例模式要实现的效果就是,对于应用单例模式的类,整个程序中只存在一个实例化对象go并不是一种面向对象的语言,所以我们使
wiki百科: 单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
单例模式要实现的效果就是,对于应用单例模式的类,整个程序中只存在一个实例化对象
go并不是一种面向对象的语言,所以我们使用结构体来替代
有几种方式:
-
懒汉模式
-
饿汉模式
-
双重检查锁机制
下面拆分讲解:
懒汉模式
- 构建一个示例结构体
type example struct { name string } 复制代码
- 设置一个私有变量作为每次要返回的单例
var instance *example 复制代码
- 写一个可以获取单例的方法
func GetExample() *example { // 存在线程安全问题,高并发时有可能创建多个对象 if instance == nil { instance = new(example) } return instance } 复制代码
-
测试一下
func main() { s := GetExample() s.name = "第一次赋值单例模式" fmt.Println(s.name) s2 := GetExample() fmt.Println(s2.name) } 复制代码
懒汉模式存在线程安全问题,在第3步的时候,如果有多个线程同时调用了这个方法,
那么都会检测到 instance
为 nil
,就会创建多个对象,所以出现了饿汉模式...
饿汉模式
与懒汉模式类似,不再多说,直接上代码
// 构建一个结构体,用来实例化单例 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.Once
的 Do
方法可以实现在程序运行过程中只运行一次其中的回调,所以最终简化的代码如下:
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) } 复制代码
单例模式是开发中经常用到的设计模式,我在制作自己的web框架 silsuer/bingo 的时候 在环境变量控制、配置项控制等位置都用到了这种模式。
想把所有 设计模式 使用golang实现一遍,开了个新坑 silsuer/golang-design-patterns , 这是第一篇,以后会陆续更新,需要请自取~
此文章的源码都在这个仓库中: golang设计模式
打个广告,推荐一下自己写的 go web框架 bingo ,求star,求PR ~
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 设计模式——订阅模式(观察者模式)
- 设计模式-简单工厂、工厂方法模式、抽象工厂模式
- java23种设计模式-门面模式(外观模式)
- 设计模式-享元设计模式
- Java 设计模式之工厂方法模式与抽象工厂模式
- JAVA设计模式之模板方法模式和建造者模式
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
未来世界的幸存者
阮一峰 / 人民邮电出版社 / 2018-6-1 / 39.00 元
本书为阮一峰博客文集,主要收录的是作者对技术变革的影响的一些思考,希望能够藉此书让读者意识到世界正在剧烈变化,洪水就在不远处,从而早早准备出路。本书适合所有乐于思考的读者。一起来看看 《未来世界的幸存者》 这本书的介绍吧!