定义

一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例模式。当某些数据只需要在系统中保留一份的时候,可以选择使用单例模式。

饿汉式

饿汉式的实现方式比较简单。在类加载的时候,静态实例就已经创建并初始化好了,所以,实例的创建过程是线程安全的。如果实例占用资源多,按照 fail-fast 的设计原则(有问题及早暴露),那我们也希望在程序启动时就将这个实例初始化好。如果资源不够,就会在程序启动的时候触发报错,我们可以立即去修复。这样也能避免在程序运行一段时间后,突然因为初始化这个实例占用资源过多,导致系统崩溃,影响系统的可用性。

下面是一个典型的饿汉式单例模式:

  1. package singleton
  2. import "sync/atomic"
  3. type HungrySingleton struct {
  4. data uint64
  5. }
  6. var hungry = &HungrySingleton{}
  7. func HungrySingletonInstance() *HungrySingleton {
  8. return hungry
  9. }

懒汉式

懒汉式单例模式可以延迟加载类实例,如果想要在使用类实例的时候再创建,可以采用这种方式来实现单例模式。不过如果类实例初始化时间比较长,可能会对效率有一定的影响。

  1. package singleton
  2. import (
  3. "sync"
  4. "sync/atomic"
  5. )
  6. type LazySingleton struct {
  7. data uint64
  8. }
  9. var lazySingleton *LazySingleton
  10. var lock sync.Mutex
  11. func init() {
  12. lock = sync.Mutex{}
  13. }
  14. func LazySingletonInstance() *LazySingleton {
  15. lock.Lock()
  16. defer lock.Unlock()
  17. if lazySingleton == nil {
  18. lazySingleton = &LazySingleton{}
  19. }
  20. return lazySingleton
  21. }

双重检测懒汉式

上面面这种实现方式比较简单粗暴,每次获取实例的时候都需要加锁,这会大大增加时间开销,效率十分低下。我们可以改进下,创建实例的时候,先判断实例是否已经创建,如果没有创建再进入创建流程,这样会减少等锁的次数,增加效率。

  1. package singleton
  2. import "sync"
  3. type LockCheckLazySingleton struct {
  4. data int64
  5. }
  6. var ll sync.Mutex
  7. var lcSingleton *LockCheckLazySingleton
  8. func init() {
  9. ll = sync.Mutex{}
  10. }
  11. func LockCheckLazySingletonInstance() *LockCheckLazySingleton {
  12. if lcSingleton == nil {
  13. ll.Lock()
  14. defer ll.Unlock()
  15. if lcSingleton == nil {
  16. lcSingleton = &LockCheckLazySingleton{}
  17. }
  18. }
  19. return lcSingleton
  20. }

在 golang 中,我们可以使用 sync.Once 来简化判断流程:

  1. package singleton
  2. import (
  3. "sync"
  4. "sync/atomic"
  5. )
  6. type CheckLazySingleton struct {
  7. data uint64
  8. }
  9. var checkLazySingleton *CheckLazySingleton
  10. var checkOnce = sync.Once{}
  11. func CheckLazySingletonInstance() *CheckLazySingleton {
  12. if checkLazySingleton == nil {
  13. checkOnce.Do(func() {
  14. checkLazySingleton = &CheckLazySingleton{}
  15. })
  16. }
  17. return checkLazySingleton
  18. }

sync.Once 使用原子操作来模拟锁的效果,来判断实例是否已经创建,和 LockCheckLazySingleton 中直接判断实例相比,并没有太多的性能开销,反而还可以避免并发时更多的运行实例进入等锁的状态,这种方式理论上效率会比上面那种方式更高一点。

性能测试

我们可以编写一个 benchmark 测试一下这几种单例模式实现方式的效率怎么样:

  1. func BenchmarkHungrySingletonInstance(b *testing.B) {
  2. b.RunParallel(func(pb *testing.PB) {
  3. for pb.Next() {
  4. if singleton.HungrySingletonInstance() != singleton.HungrySingletonInstance() {
  5. b.Errorf("different instance")
  6. }
  7. }
  8. })
  9. }
  10. func BenchmarkLazySingletonInstance(b *testing.B) {
  11. b.RunParallel(func(pb *testing.PB) {
  12. for pb.Next() {
  13. if singleton.LazySingletonInstance() != singleton.LazySingletonInstance() {
  14. b.Errorf("different instance")
  15. }
  16. }
  17. })
  18. }
  19. func BenchmarkLockCheckLazySingletonInstance(b *testing.B) {
  20. b.RunParallel(func(pb *testing.PB) {
  21. for pb.Next() {
  22. if singleton.LockCheckLazySingletonInstance() != singleton.LockCheckLazySingletonInstance() {
  23. b.Errorf("different instance")
  24. }
  25. }
  26. })
  27. }
  28. func BenchmarkCheckLazySingletonInstance(b *testing.B) {
  29. b.RunParallel(func(pb *testing.PB) {
  30. for pb.Next() {
  31. if singleton.CheckLazySingletonInstance() != singleton.CheckLazySingletonInstance() {
  32. b.Errorf("different instance")
  33. }
  34. }
  35. })
  36. }

输出结果如下所示:

  1. D:\git\design-pattern\singleton>go test -bench=.
  2. goos: windows
  3. goarch: amd64
  4. pkg: design-pattern/singleton
  5. cpu: Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
  6. BenchmarkHungrySingletonInstance-4 1000000000 0.5729 ns/op
  7. BenchmarkLazySingletonInstance-4 17065185 71.58 ns/op
  8. BenchmarkLockCheckLazySingletonInstance-4 377396236 2.878 ns/op
  9. BenchmarkCheckLazySingletonInstance-4 536262001 2.330 ns/op
  10. PASS
  11. ok design-pattern/singleton 4.733s

我们可以看到,饿汉模式的效率是最高的,因为它的实例是在程序启动的时候就已经初始化好了,调用实例的过程省去了很多判断的过程。而直接加锁的懒汉模式是效率最低的,锁的开销是十分大的,实际使用中,如果有需要使用懒汉式的单例模式,也不推荐这种实现方式。后面两种优化的懒汉模式中,使用 once 的效率会更高一点,这种实现方式更加值得推荐。

单例模式的问题及替代方案

单例模式的问题

  1. 单例对 OOP 特性(封装、继承、多态、抽象)的支持不友好。单例模式违反了基于接口而非实现的设计原则。
  2. 单例会隐藏类之间的依赖关系。单例类不需要显示创建、不需要依赖参数传递,在代码比较复杂的情况下,调用关系就会非常隐蔽。
  3. 单例对代码的扩展性不友好,在某些情况下会影响代码的扩展性、灵活性。
  4. 单例对代码的可测试性不友好。单例类持有的变量通常是全局变量,被所有的代码共享,这不方便编写单元测试。
  5. 单例不支持有参数的构造函数,因为单例类的对象只初始化一次,即使构造函数有参数也只接收第一次的参数。

替代方案

可以通过工厂模式、IOC容器(JAVA)等方式

[Design Pattern With Go]设计模式-单例模式的更多相关文章

  1. Design Pattern Iterator 迭代器设计模式

    这个设计模式感觉很easy,我们平时敲代码的时候也是常常须要调用iterator的,C++和Java都是. 所以感觉没什么特别的.就是须要模仿C++或者Java的iterator类的功能吧. 这里简单 ...

  2. Design Pattern Memo 备忘录设计模式

    本设计模式就是简单地记录当前状态.然后利用记录的数据恢复. 比方首先我们有一个类.类须要记录当前状态进行相关的工作的: class Memo; class Human { public: string ...

  3. design pattern Builder 生成器设计模式

    其实设计模式可以学习很有趣,你并不需要有这么难啃旱地FOG对我来说,当然,这些都是健康的骨骼啃啃. 在本文中,建造者模式设计一个搞笑的一幕.根据这一模型来学习功夫的方法,哈哈. 基类的第一,设计.那么 ...

  4. Design Pattern Command 命令设计模式

    这种设计模式是使用不同类的包裹不同的命令,达到什么样的命令执行什么操作. 有可能进一步利用map您最喜欢的对接命令字. 正在运行的类实际上已经包含了操作的所有需求,例如: class SuperMak ...

  5. Design Pattern Bridge 桥设计模式

    桥设计模式事实上就是一个简单的has a relationship.就是一个类拥有还有一个类,并使用还有一个类实现须要的功能. 比方遥控器和电视之间能够使用桥设计模式达到能够使用同一个遥控器控制多台电 ...

  6. Flyweight Design Pattern 共享元设计模式

    就是利用一个类来完毕多种任务.不用每次都创建一个新类. 个人认为这个设计模式在C++里面,好像能够就使用一个函数取代,利用重复调用这个函数完毕任务和重复利用这个类,好像几乎相同. 只是既然是一个设计模 ...

  7. Design Pattern Adaptor 适配器设计模式

    适配器设计模式是为了要使用一个旧的接口,或许这个接口非常难用,或许是和新的更新的接口不兼容,所以须要设计一个适配器类,然后就能够让新旧的接口都统一. 就是这种一个图: watermark/2/text ...

  8. [Design Pattern With Go]设计模式-工厂模式

    这次介绍的设计模式是工厂模式,这是一个比较常见的创建型模式.一般情况下,工厂模式分为三种:简单工厂.工厂方法和抽象工厂,下面慢慢举例介绍下. 简单工厂 考虑一个加密程序的应用场景,一个加密程序可能提供 ...

  9. 说说设计模式~大话目录(Design Pattern)

    回到占占推荐博客索引 设计模式(Design pattern)与其它知识不同,它没有华丽的外表,没有吸引人的工具去实现,它是一种心法,一种内功,如果你希望在软件开发领域有一种新的突破,一个质的飞越,那 ...

随机推荐

  1. SwiftUI & MVVM

    SwiftUI & MVVM design paradigm / 设计模式 MVVM Model View ViewModel MVVM Architecture 架构 MVC Model V ...

  2. document.getElementById & document.querySelector

    document.getElementById & document.querySelector https://developer.mozilla.org/en-US/docs/Web/AP ...

  3. taro 滚动事件

    taro 滚动事件 taro scroll bug ScrollView https://nervjs.github.io/taro/docs/components/viewContainer/scr ...

  4. 宝塔部署Nestjs

    1. 在宝塔上下载pm2 2. 打包你的服务端代码 "npm run build && cp ./package.json ./dist/" 3. 在宝塔文件&qu ...

  5. 【C#】反射的用法及效率对比

    反射实例化类 public class Person { public string Name { get; set; } public Person(string name) { this.Name ...

  6. 未来,Baccarat将如何拓展生态版图?

    Baccarat最近几度冲上DeFi版面的热搜,一是因为BGV价格不断的上涨,二是因为生态建设者的不断涌入.可以说,当下的Baccarat,实在是太火爆了.那么在未来,Baccarat还将会持续火爆吗 ...

  7. 直播预告 | 全面的审计分析和权限管控——CloudQuery年终发布!

    2020年9月,CloudQuery 发布. 针对开发.运维人员面临的如何高效便捷访问.操作管理数据的问题,我们设计并研发了云原生安全数据操作平台,CloudQuery 就此诞生! 2020年结束之际 ...

  8. std::vector与std::list效能对比(基于c++11)

    测试对象类型不同,数量级不同时,表现具有差异: 测试数据对象为std::function时: test: times(1000)vector push_back time 469 usvector e ...

  9. 【HTB靶场系列】靶机Carrier的渗透测试

    出品|MS08067实验室(www.ms08067.com) 本文作者:大方子(Ms08067实验室核心成员) Hack The Box是一个CTF挑战靶机平台,在线渗透测试平台.它能帮助你提升渗透测 ...

  10. react新手入坑

    1.vscode保存react项目的时候由于js-css-html插件格式化代码导致react代码缩进错误 解决方法:禁用js-css-html插件 2.react和vue不同,react方法的定义需 ...