定义

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

饿汉式

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

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

package singleton

import "sync/atomic"

type HungrySingleton struct {
data uint64
} var hungry = &HungrySingleton{} func HungrySingletonInstance() *HungrySingleton {
return hungry
}

懒汉式

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

package singleton

import (
"sync"
"sync/atomic"
) type LazySingleton struct {
data uint64
} var lazySingleton *LazySingleton
var lock sync.Mutex func init() {
lock = sync.Mutex{}
} func LazySingletonInstance() *LazySingleton {
lock.Lock()
defer lock.Unlock()
if lazySingleton == nil {
lazySingleton = &LazySingleton{}
}
return lazySingleton
}

双重检测懒汉式

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

package singleton

import "sync"

type LockCheckLazySingleton struct {
data int64
} var ll sync.Mutex
var lcSingleton *LockCheckLazySingleton func init() {
ll = sync.Mutex{}
} func LockCheckLazySingletonInstance() *LockCheckLazySingleton {
if lcSingleton == nil {
ll.Lock()
defer ll.Unlock()
if lcSingleton == nil {
lcSingleton = &LockCheckLazySingleton{}
}
}
return lcSingleton
}

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

package singleton

import (
"sync"
"sync/atomic"
) type CheckLazySingleton struct {
data uint64
} var checkLazySingleton *CheckLazySingleton
var checkOnce = sync.Once{} func CheckLazySingletonInstance() *CheckLazySingleton {
if checkLazySingleton == nil {
checkOnce.Do(func() {
checkLazySingleton = &CheckLazySingleton{}
})
} return checkLazySingleton
}

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

性能测试

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

func BenchmarkHungrySingletonInstance(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if singleton.HungrySingletonInstance() != singleton.HungrySingletonInstance() {
b.Errorf("different instance")
}
}
})
} func BenchmarkLazySingletonInstance(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if singleton.LazySingletonInstance() != singleton.LazySingletonInstance() {
b.Errorf("different instance")
}
}
})
} func BenchmarkLockCheckLazySingletonInstance(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if singleton.LockCheckLazySingletonInstance() != singleton.LockCheckLazySingletonInstance() {
b.Errorf("different instance")
}
}
})
} func BenchmarkCheckLazySingletonInstance(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
if singleton.CheckLazySingletonInstance() != singleton.CheckLazySingletonInstance() {
b.Errorf("different instance")
}
}
})
}

输出结果如下所示:

D:\git\design-pattern\singleton>go test -bench=.
goos: windows
goarch: amd64
pkg: design-pattern/singleton
cpu: Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
BenchmarkHungrySingletonInstance-4 1000000000 0.5729 ns/op
BenchmarkLazySingletonInstance-4 17065185 71.58 ns/op
BenchmarkLockCheckLazySingletonInstance-4 377396236 2.878 ns/op
BenchmarkCheckLazySingletonInstance-4 536262001 2.330 ns/op
PASS
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. 前端知名人士 All In One

    前端知名人士 All In One 前端名人堂(中国) https://node.fequan.com/lecturer/ JavaScript的过去.现在和未来 1995年,Brendan Eich ...

  2. React Hooks vs React Class vs React Function All In One

    React Hooks vs React Class vs React Function All In One React Component Types React Hooks Component ...

  3. OAuth 2.0 All In One

    OAuth 2.0 All In One 授权类型 授权代码 隐式 密码凭证 客户端凭证 授权码 授权码授予类型要求用户向提供者进行身份验证-然后将授权码发送回客户端应用程序,提取并与提供者交换以获取 ...

  4. Twitter 分享

    Twitter 分享 Twitter Share API https://twitter.com/intent/tweet?url= &text= demo ?url= https://www ...

  5. setTimeout 实现原理, 机制

    setTimeout 实现原理, 机制 JS 执行机制说起 浏览器(或者说 JS 引擎)执行 JS 的机制是基于事件循环. 由于 JS 是单线程,所以同一时间只能执行一个任务,其他任务就得排队,后续任 ...

  6. HTTP/1.1 & HTTP/2 & webpack

    HTTP/1.1 & HTTP/2 & webpack Bundling your application is especially powerful for HTTP/1.1 cl ...

  7. javascript disable scroll event

    javascript disable scroll event Document: scroll event https://developer.mozilla.org/en-US/docs/Web/ ...

  8. 12月17日BGV币行情分析

    目前BGV收于353.95美金,较前一交易日上涨28.25%. 非小号数据显示,BGV最大客户aofexpay.ngk出现+490.2349的持币变化,其次减仓数额均不大,分别为-80.1,-30,- ...

  9. NGK.IO超级节点是我们掌握的下一个财富密码吗?

    从日前NGK.IO发布的新闻中,我们捕捉到了一个非常重要的信息,那就是反复被提到的"超级节点".很多人都对这个"超级节点"一头雾水,这个"超级节点&q ...

  10. PyTorch 自定义数据集

    准备数据 准备 COCO128 数据集,其是 COCO train2017 前 128 个数据.按 YOLOv5 组织的目录: $ tree ~/datasets/coco128 -L 2 /home ...