单例模式恐怕是最为人熟知的一种设计模式了。它同样也是创建型模式的一种。当某个struct只允许有一个实例的时候,我们会用到这种设计模式。这个struct的唯一的实例被称为单例对象。下面是需要创建单例对象的一些场景:

  • 数据库实例:一般在开发中,对于一个应用,我们通常只需要一个数据库对象实例
  • 日志实例:同样,对于一个应用来说,日志操作对象也只需要一个实例

单例对象通常在struct初始化的时候创建。通常,如果某个struct只需要创建一个实例的时候,会为其定义一个getInstance()方法,创建的单例实例会通过这个方法返回给调用者。

因为Go语言中有goroutines,它会给单例模式的应用带来一些麻烦。我们在构建单例模式的时候必须要考虑到在多个goroutines访问struct的getInstance()方法的时候应该返回相同的实例。下面的代码演示了如何正确的创建一个单例对象:

var lock = &sync.Mutex{}

type single struct {
} var singleInstance *single func getInstance() *single {
if singleInstance == nil {
lock.Lock()
defer lock.Unlock()
if singleInstance == nil {
fmt.Println("Creting Single Instance Now")
singleInstance = &single{}
} else {
fmt.Println("Single Instance already created-1")
}
} else {
fmt.Println("Single Instance already created-2")
}
return singleInstance
}

以上的代码保证了single struct只会有一个实例。代码中有几处可以注意下:

  1. getInstance()方法的起始处首先检查了下singleInstance是否为nil。这样每次调用getInstance()方法的时候可以避免执行“锁”操作。因为“锁”相关的操作比较耗资源,会影响性能,因此越少调用越好。
  2. singleInstance对象在“锁”作用区间内创建,可以避免goroutines的影响。
  3. 在获取到“锁”资源后,程序中又一次校验了singleInstance对象是否为空。这是因为可能会有多个goroutines通过第一次校验,二次校验可以保证只有一个goroutine创建单例,不然每个goroutine都有可能会创建一个single struct实例。

完整代码在这里:

single.go

import (
"fmt"
"sync"
) var lock = &sync.Mutex{} type single struct {
} var singleInstance *single func GetInstance() *single {
if singleInstance == nil {
lock.Lock()
defer lock.Unlock()
if singleInstance == nil {
fmt.Println("Creating Single Instance Now")
singleInstance = &single{}
} else {
fmt.Println("Single Instance already created-1")
}
} else {
fmt.Println("Single Instance already created-2")
}
return singleInstance
}

  main.go

import (
"fmt"
) func main() {
for i := 0; i < 100; i++ {
go GetInstance()
}
// Scanln is similar to Scan, but stops scanning at a newline and
// after the final item there must be a newline or EOF.
fmt.Scanln()
}

  输出内容:

Creating Single Instance Now
Single Instance already created-1
Single Instance already created-2
Single Instance already created-1
Single Instance already created-1
Single Instance already created-1
Single Instance already created-1
Single Instance already created-1
Single Instance already created-1
Single Instance already created-2
Single Instance already created-2
...

简单说明下:

  • 输出内容中只有一行“Creating Single Instance Now”这样的输出,这说明只有一个goroutine能够创建一个singlestruct的实例。
  • 输出内容中有多行“Single Instance already created-1”,说明有多个goroutines通过第一次检查singleInstance对象是否为空的校验,它本来都有机会创建单例。
  • 最后输出的都是“Single Instance already created-2”,意味着单例已创建完成,之后的goroutines都无法再通过首次校验。

除了锁+二次校验的方式,还有其它创建单例的方法,我们来看一下:

基于init()函数

init()函数中创建单例。因为一个包中每个文件的init()函数都只会调用一次,这样就可以保证只有一个实例会被创建。看下代码:

import (
"fmt"
"log"
) type single struct {
} var singleInstance *single func init() {
fmt.Println("Creating Single Instance Now")
singleInstance = &single{}
} func GetInstance() *single {
if singleInstance == nil {
log.Fatal("Single Instance is nil")
} else {
fmt.Println("Single Instance already created-2")
}
return singleInstance
}

这应该就是go语言中的懒汉式单例创建方法了。如果不介意过早创建实例造成的资源占用,推荐使用这种方法创建单例。

通过sync.Once

sync.Once中的代码会被保证只执行一次,这完全可以用来创建单例。代码如下:

import (
"fmt"
"sync"
) var once sync.Once type single struct {
} var singleInstance *single func GetInstance() *single {
if singleInstance == nil {
once.Do(
func() {
fmt.Println("Creating Single Instance Now")
singleInstance = &single{}
})
fmt.Println("Single Instance already created-1")
} else {
fmt.Println("Single Instance already created-2")
}
return singleInstance
}

相比二次校验的方式,这里的代码可以说非常简洁了。这也是我非常建议使用的一种单例创建方式。

输出内容为:

Creating Single Instance Now
Single Instance already created-1
Single Instance already created-2
Single Instance already created-2
Single Instance already created-1
Single Instance already created-2
Single Instance already created-1
Single Instance already created-1
Single Instance already created-2
Single Instance already created-1
Single Instance already created-2
Single Instance already created-1
Single Instance already created-2
Single Instance already created-2
Single Instance already created-2
Single Instance already created-2
Single Instance already created-2
Single Instance already created-2
Single Instance already created-2
Single Instance already created-2
...

简单说明下: 

  • 输出的内容和二次校验的方式差不多,仍然存在多行“Single Instance already created-1”这样的输出,说明有多个goroutine通过了if校验
  • 输出内容中只有一行“Creating Single Instance Now”,说明只有一个goroutine能够创建实例。

代码已上传至GitHub:zhyea / go-patterns / singleton-pattern

End!

GoLang设计模式04 - 单例模式的更多相关文章

  1. Golang 常见设计模式之单例模式

    之前我们已经看过了 Golang 常见设计模式中的装饰和选项模式,今天要看的是 Golang 设计模式里最简单的单例模式.单例模式的作用是确保无论对象被实例化多少次,全局都只有一个实例存在.根据这一特 ...

  2. 设计模式之单例模式(Singleton)

    设计模式之单例模式(Singleton) 设计模式是前辈的一些经验总结之后的精髓,学习设计模式可以针对不同的问题给出更加优雅的解答 单例模式可分为俩种:懒汉模式和饿汉模式.俩种模式分别有不同的优势和缺 ...

  3. GJM : C#设计模式(1)——单例模式

    感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...

  4. java设计模式之单例模式(几种写法及比较)

    概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建 ...

  5. 每天一个设计模式-4 单例模式(Singleton)

    每天一个设计模式-4 单例模式(Singleton) 1.实际生活的例子 有一天,你的自行车的某个螺丝钉松了,修车铺离你家比较远,而附近的五金店有卖扳手:因此,你决定去五金店买一个扳手,自己把螺丝钉固 ...

  6. 设计模式之单例模式的简单demo

    /* * 设计模式之单例模式的简单demo */ class Single { /* * 创建一个本类对象. * 和get/set方法思想一样,类不能直接调用对象 * 所以用private限制权限 * ...

  7. 设计模式之单例模式——Singleton

                        设计模式之单例模式--Singleton 设计意图: 保证类仅有一个实例,并且可以供应用程序全局使用.为了保证这一点,就需要这个类自己创建自己的对象,并且对外有 ...

  8. 10月27日PHP加载类、设计模式(单例模式和工厂模式)、面向对象的六大原则

    加载类可以使用include.require.require_once三种中的任意一种,每个关键字都有两种方法,但是这种方法的缺点是需要加载多少个php文件,就要写多少个加载类的方法.一般也就需要加载 ...

  9. java 23 - 2 设计模式之单例模式

    单例模式:保证类在内存中只有一个对象. 如何保证类在内存中只有一个对象呢?  A:把构造方法私有  B:在成员位置自己创建一个对象  C:通过一个公共的方法提供访问 单例模式之饿汉式: (一进来就造对 ...

随机推荐

  1. 八数码难题之 A* 算法

    人生第一个A*算法-好激动-- 八数码难题--又称八数码水题,首先要理解一些东西: 1.状态可以转化成整数,比如状态: 1 2 3 4 5 6 7 8 0 可以转化成:123456780这个整数 2. ...

  2. 靶机CH4INRULZ_v1.0.1

    nmap开路. root@kali:~# nmap -sP 192.168.1.* //拿到靶机地址192.168.1.8 root@kali:~# nmap -p- -sS -v -sV 192.1 ...

  3. Bugku-web-字符?正则?

    题目意思很明显,根据给出的规则构造出合理的正则表达式然后通过get方式提交弹出Flag. i:字体大小 /..../表示开始和结束. .号表示匹配0-9任一数字 *号表示重复前一个字符多次 {4,7} ...

  4. 2020互联网寒冬之下,作为一个Android老码农,是如何进入腾讯的?

    由于众所周知的原因,原生Android开发如今已经日渐凋敝,作为一个Android程序员,不仅要会Java,Kotlin,JavaScript,Css,Html,还要会Flutter,C++,FFmp ...

  5. git 切换分支 本地代码失踪找回办法

    解决方案: https://blog.csdn.net/hupoling/article/details/79017382 主要步骤: git reflog 然后找到之前commit的分支 git c ...

  6. RPM包方式安装Oracle21c的方法

    RPM包方式安装Oracle21c的方法 前言 北京时间2021.8.14 Oracle发布了最新的数据库版本Oracle21c, Oracle规划不再发布Oracle20c和Oracle22c, 直 ...

  7. 设计模式:单例模式的使用和实现(JAVA)

    单例模式的使用 jdk和Spring都有实现单例模式,这里举的例子是JDK中Runtime这个类 Runtime的使用 通过Runtime类可以获取JVM堆内存的信息,还可以调用它的方法进行GC. p ...

  8. 【原创】利用动态二进制加密实现新型一句话木马之PHP篇

    概述 本系列文章重写了java..net.php三个版本的一句话木马,可以解析并执行客户端传递过来的加密二进制流,并实现了相应的客户端工具.从而一劳永逸的绕过WAF或者其他网络防火墙的检测. 本来是想 ...

  9. 安全工具推荐之sqlmap tamper&sqlmap api

    我发现总有一些人喜欢问sqlmap的tamper脚本,问完工具问参数,问完参数问脚本...... 你这个问题问的水平就很艺术,让我一时不知从何说起...... 说一下在sqlmap的使用过程中,个人了 ...

  10. CTFre-getit-WP

    攻防世界getit-WP 日子忙起来人也就忙,CTF慢慢刷,慢就是快. 下载之后,也没管别的直接就IDA打开:下载之后,也没管别的直接就IDA打开: 随便点点看得到三个可以字符串.F5看看: 懂个大概 ...