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

JSON 2022-06-30 14:45:07 9381

之前我们已经看过了 Golang 常见设计模式中的装饰和选项模式,今天要看的是 Golang 设计模式里最简单的单例模式。单例模式的作用是确保无论对象被实例化多少次,全局都只有一个实例存在。根据这一特性,我们可以将其应用到全局唯一性配置、数据库连接对象、文件访问对象等。Go 语言实现单例模式的方法有很多种,下面我们就一起来看一下。

饿汉式

饿汉式实现单例模式非常简单,直接看代码:


package singleton
    type singleton struct{}
    var instance = &singleton{}
    func GetSingleton() *singleton {
       return instance
}

singleton 包在被导入时会自动初始化 instance 实例,使用时通过调用 singleton.GetSingleton() 函数即可获得 singleton 这个结构体的单例对象。

这种方式的单例对象是在包加载时立即被创建,所以这个方式叫作饿汉式。与之对应的另一种实现方式叫作懒汉式,懒汉式模式下实例会在第一次被使用时被创建。

需要注意的是,尽管饿汉式实现单例模式的方式简单,但大多数情况下并不推荐。因为如果单例实例化时初始化内容过多,会造成程序加载用时较长。

懒汉式

接下来我们再来看下如何通过懒汉式实现单例模式:


package singleton
type singleton struct{}
var instance *singleton
func GetSingleton() *singleton {
    if instance == nil {
        instance = &singleton{}
    }
    return instance
}

相较于饿汉式的实现,懒汉式将实例化 singleton 结构体部分的代码移到了 GetSingleton() 函数内部。这样能够将对象实例化的步骤延迟到 GetSingleton() 第一次被调用时。

不过通过 instance == nil 的判断来实现单例并不十分可靠,如果有多个 goroutine 同时调用 GetSingleton() 就无法保证并发安全。

支持并发的单例

如果你使用 Go 语言写过并发编程,应该很快能想到该如何解决懒汉式单例模式并发安全问题,比如像下面这样:


package singleton
import "sync"
type singleton struct{}
var instance *singleton
var mu sync.Mutex
func GetSingleton() *singleton {
    mu.Lock()
    defer mu.Unlock()
    if instance == nil {
        instance = &singleton{}
    }
    return instance
}

上面代码的修改是通过加锁机制,即在 GetSingleton() 函数最开始加了如下两行代码:

mu.Lock()
  defer mu.Unlock()

加锁的机制可以有效保证这个实现单例模式的函数是并发安全的。

不过使用了锁机制也带来了一些问题,这让每次调用 GetSingleton() 时程序都会进行加锁、解锁的步骤,从而导致程序性能的下降。

双重锁定

加锁会导致程序性能下降,但又不用锁又无法保证程序的并发安全。为了解决这个问题有人提出了双重锁定(Double-Check Locking)的方案:

package singleton
import "sync"
type singleton struct{}
var instance *singleton
var mu sync.Mutex
func GetSingleton() *singleton {
    if instance == nil {
        mu.Lock()
        defer mu.Unlock()
        if instance == nil {
            instance = &singleton{}
        }
    }
    return instance
}

通过上面的可以看到,所谓双重锁定实际上就是在程序加锁前又加了一层 instance == nil 判断,通过这种方式来兼顾性能和安全两个方面。不过这让代码看起来有些奇怪,外层已经判断了 instance == nil,但是加锁后又进行了第二次 instance == nil  判断。

其实外层的 instance == nil 判断是为了提高程序的执行效率,免去原来每次调用 GetSingleton() 都上锁的操作,将加锁的粒度更加精细化。简单说就是如果 instance 已经存在,则无需进入 if 逻辑,程序直接返回 instance 即可。而内层的 instance == nil  判断则考虑了并发安全,考虑到万一在极端情况下,多个 goroutine 同时走到了加锁这一步,内层判断会在这里起到作用。

Gopher 惯用方案

虽然双重锁定机制兼顾和性能和并发安全,但显然代码有些丑陋,不符合广大 Gopher 的期待。好在 Go 语言在 sync 包中提供了 Once 机制能够帮助我们写出更加优雅的代码:

package singleton
import "sync"
type singleton struct{}
var instance *singleton
var once sync.Once
func GetSingleton() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })
    return instance
}

Once 是一个结构体,在执行 Do 方法的内部通过 atomic 操作和加锁机制来保证并发安全,且 once.Do 能够保证多个 goroutine 同时执行时 &singleton{} 只被创建一次。

其实 Once 并不神秘,其内部实现跟上面使用的双重锁定机制非常类似,只不过把 instance == nil 换成了 atomic 操作,感兴趣的同学可以查看下其对应源码。

总结

以上就是 Go 语言中实现单例模式的几种常用套路,经过对比可以得出结论,最推荐的方式是使用 once.Do 来实现,sync.Once 包帮我们隐藏了部分细节,却可以让代码可读性得到很大提升。

文章来自又拍云投稿


版权所属:SO JSON在线解析

原文地址:https://www.sojson.com/blog/394.html

转载时必须以链接形式注明原始出处及本声明。

本文主题:

如果本文对你有帮助,那么请你赞助我,让我更有激情的写下去,帮助更多的人。

关于作者
一个低调而闷骚的男人。
相关文章
Golang 常见设计模式——装饰模式详细讲解
JavaScript怎么识别360浏览器?JS识别360急速模式方案,360流氓浏览器
SpringMVC + Freemarker 版的使用。
Java客户端Jedis 对Redis的的八种调用方式(事务、管道、分布)介绍及测试
MyEclipse8.5,MyEclipse10,安装ERMaster 建插件
ik分词器如何实现糊匹配
Javascript块化编程,本站使用 require.js 优化JS加载, require.js下载
在线HTTP接口测试 - HTTP GET/POST拟请求测试工具【更新说明】
Redis 单线程型分析
Java拟WSS websocket ssl请求,Java WSS拟请求代码示例
最新文章
Linux I/O重定向 1767
Ruby 循环 - while、for、until、break、redo 和 retry 711
Node.js:全局对象 517
如何使用终端检查Linux上的内存使用情况 635
JavaScript对象详细剖析 300
Python print() 函数 409
PHP if/else/elseif 语句 407
HTML5 Canvas弧线教程 387
Java赋值运算符 431
XML内部实体和外部实体 464
最热文章
最新MyEclipse8.5注册码,有效期到2020年 (已经更新) 686836
苹果电脑Mac怎么恢复出厂系统?苹果系统怎么重装系统? 675081
免费天气API,天气JSON API,不限次数获取十五天的天气预报 615785
免费天气API,全国天气 JSON API接口,可以获取五天的天气预报 611117
Jackson 时间格式化,时间注解 @JsonFormat 用法、时差问题说明 555623
我为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比 510028
Elasticsearch教程(四) elasticsearch head 插件安装和使用 481399
Jackson 美化输出JSON,优雅的输出JSON数据,格式化输出JSON数据... ... 269208
Java 信任所有SSL证书,HTTPS请求抛错,忽略证书请求完美解决 244787
Elasticsearch教程(一),全程直播(小白级别) 227489
支付扫码

所有赞助/开支都讲公开明细,用于网站维护:赞助名单查看

查看我的收藏

正在加载... ...