你真的会用go语言写单例模式吗?
最近在学习Golang,想着可以就以前的知识做一些串通,加上了解到go语言也是面向对象编程语言之后。在最近的开发过程中,我碰到一个问题,要用go语言实现单例模式。本着“天下知识,同根同源”(我瞎掰的~),我心想,这有什么难的,可是真正做起来,还是碰到了不少问题。
下面是我的经历:
1.我先是完成了我的第一版单例模式,就是非并发,最简单的一种,懒汉模式:
var instance *single
type single struct{
Name string
}
func GetInstance()*single{
if m == nil{
m = &single{}
}
return m
}
func main(){
a := GetInstance()
a.Name = "a"
b := GetInstance()
b.Name = "b"
fmt.Println(&a.Name, a)
fmt.Println(&b.Name, b)
fmt.Printf("%p %T\n", a, a)
fmt.Printf("%p %T\n", b, b)
}
结果如下:
0xc04203e1b0 &{b}
0xc04203e1b0 &{b}
0xc04203e1b0 *main.single
0xc04203e1b0 *main.single
可以看到,我们已经实现了简单的单例模式,我们申请了两次实例,在改变一个第二个实例的字段之后,第一个也随之改变了。而且从他们的地址都相同也可以看出是同一个对象。但是,这样简陋的单例模式在并发下就容易出错,非线程安全的。
现在我们是在并发的情况下去调用的 GetInstance
函数,现在恰好第一个goroutine执行到m = &Manager {}
这句话之前,第二个goroutine也来获取实例了,第二个goroutine去判断m是不是nil,因为m = &Manager{}
还没有来得及执行,所以m肯定是nil,现在出现的问题就是if中的语句可能会执行两遍!
2.紧接着我们做了一些改进,给单例模式加了锁:
var m *single
var lock sync.Mutex
type single struct{
Name string
}
func GetInstance()*single{
lock.Lock()
defer lock.Unlock()
if m == nil{
m = &single{}
}
return m
}
结果同上。
与此同时,新的问题出现了,在高并发环境下,现在不管什么情况下都会上一把锁,而且加锁的代价是很大的,有没有办法继续对我们的代码进行进一步的优化呢?
3.双重锁机制:
var m *single
var lock sync.Mutex
type single struct{
Name string
}
func GetInstance()*single{
if m == nil{
lock.Lock()
defer lock.Unlock()
if m == nil{
m = &single{}
}
}
return m
}
这次我们用了两个判断,而且我们将同步锁放在了条件判断之后,这样做就避免了每次调用都加锁,提高了代码的执行效率。理论上写到这里已经是很完美的单例模式了,但是我们在go语言里,我们有一个很优雅的写法。
4.sync包里的Once.Do()方法
var m *single
var once sync.Once type single struct{
Name string
}
func GetInstance()*single{
once.Do(func() {
m = &single{}
})
return m
}
Once.Do方法的参数是一个函数,这里我们给的是一个匿名函数,在这个函数中我们做的工作很简单,就是去赋值m变量,而且go能保证这个函数中的代码仅仅执行一次!
以后在用go语言写单例模式的时候,可不要再傻傻的去使用前面那些例子了,既然已经有了优雅又强大的方法,我们直接用就完了。
最新文章
- Kooboo CMS技术文档之三:切换数据存储方式
- 我的nodejs学习之路1
- C# WM_NCMOUSELEAVE 消息触发
- java 面向对象编程-- 第十三章 反射、类加载与垃圾回收
- mac下Android开发环境搭建
- 解决Github访问超慢问题[自己留档]
- 结构体定义 typedef struct 用法详解和用法小结
- eclipse不自动弹出提示(Alt+/ 快捷键失效)
- .net 调用Oracle.Data.Access 组件提供的用于批量操作的方法
- struts2源码调试环境的搭建
- PHP面试题之算法解析
- Linux新手笔记 svn ntfs
- Spark编译与部署
- Html 模态框操作
- Linux实战教学笔记25:自动化运维工具之ansible (一)
- mysql生成百万级数量测试数据
- Postman-----设置环境变量
- MySQL数据库的基础学习
- SpringBank 开发日志 Mybatis 使用redis 作为二级缓存时,无法通过cacheEnabled=false 将其关闭
- IdentityServer4-客户端的授权模式原理分析(三)
热门文章
- POJ1083(Moving Tables)--简单模拟
- linux命令启动关闭firewalld防火墙,添加端口
- window.open打开一个新空白页面,不会自动刷新【解决方案】
- apk签名文件生成
- 判断对象是否为null
- Image Processing and Analysis_21_Scale Space:Scale-space filtering——1987
- Flutter——CircleAvatar组件(圆形头像组件)
- 02.python网络爬虫第二弹(http和https协议)
- 页面Header自适应屏幕
- 分析可变形字符串序列StringBuilder 以及 StringBuffer之默认大小与扩容