[Design Pattern With Go]设计模式-单例模式
定义
一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例模式。当某些数据只需要在系统中保留一份的时候,可以选择使用单例模式。
饿汉式
饿汉式的实现方式比较简单。在类加载的时候,静态实例就已经创建并初始化好了,所以,实例的创建过程是线程安全的。如果实例占用资源多,按照 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 的效率会更高一点,这种实现方式更加值得推荐。
单例模式的问题及替代方案
单例模式的问题
- 单例对 OOP 特性(封装、继承、多态、抽象)的支持不友好。单例模式违反了基于接口而非实现的设计原则。
- 单例会隐藏类之间的依赖关系。单例类不需要显示创建、不需要依赖参数传递,在代码比较复杂的情况下,调用关系就会非常隐蔽。
- 单例对代码的扩展性不友好,在某些情况下会影响代码的扩展性、灵活性。
- 单例对代码的可测试性不友好。单例类持有的变量通常是全局变量,被所有的代码共享,这不方便编写单元测试。
- 单例不支持有参数的构造函数,因为单例类的对象只初始化一次,即使构造函数有参数也只接收第一次的参数。
替代方案
可以通过工厂模式、IOC容器(JAVA)等方式
[Design Pattern With Go]设计模式-单例模式的更多相关文章
- Design Pattern Iterator 迭代器设计模式
这个设计模式感觉很easy,我们平时敲代码的时候也是常常须要调用iterator的,C++和Java都是. 所以感觉没什么特别的.就是须要模仿C++或者Java的iterator类的功能吧. 这里简单 ...
- Design Pattern Memo 备忘录设计模式
本设计模式就是简单地记录当前状态.然后利用记录的数据恢复. 比方首先我们有一个类.类须要记录当前状态进行相关的工作的: class Memo; class Human { public: string ...
- design pattern Builder 生成器设计模式
其实设计模式可以学习很有趣,你并不需要有这么难啃旱地FOG对我来说,当然,这些都是健康的骨骼啃啃. 在本文中,建造者模式设计一个搞笑的一幕.根据这一模型来学习功夫的方法,哈哈. 基类的第一,设计.那么 ...
- Design Pattern Command 命令设计模式
这种设计模式是使用不同类的包裹不同的命令,达到什么样的命令执行什么操作. 有可能进一步利用map您最喜欢的对接命令字. 正在运行的类实际上已经包含了操作的所有需求,例如: class SuperMak ...
- Design Pattern Bridge 桥设计模式
桥设计模式事实上就是一个简单的has a relationship.就是一个类拥有还有一个类,并使用还有一个类实现须要的功能. 比方遥控器和电视之间能够使用桥设计模式达到能够使用同一个遥控器控制多台电 ...
- Flyweight Design Pattern 共享元设计模式
就是利用一个类来完毕多种任务.不用每次都创建一个新类. 个人认为这个设计模式在C++里面,好像能够就使用一个函数取代,利用重复调用这个函数完毕任务和重复利用这个类,好像几乎相同. 只是既然是一个设计模 ...
- Design Pattern Adaptor 适配器设计模式
适配器设计模式是为了要使用一个旧的接口,或许这个接口非常难用,或许是和新的更新的接口不兼容,所以须要设计一个适配器类,然后就能够让新旧的接口都统一. 就是这种一个图: watermark/2/text ...
- [Design Pattern With Go]设计模式-工厂模式
这次介绍的设计模式是工厂模式,这是一个比较常见的创建型模式.一般情况下,工厂模式分为三种:简单工厂.工厂方法和抽象工厂,下面慢慢举例介绍下. 简单工厂 考虑一个加密程序的应用场景,一个加密程序可能提供 ...
- 说说设计模式~大话目录(Design Pattern)
回到占占推荐博客索引 设计模式(Design pattern)与其它知识不同,它没有华丽的外表,没有吸引人的工具去实现,它是一种心法,一种内功,如果你希望在软件开发领域有一种新的突破,一个质的飞越,那 ...
随机推荐
- NGK.IO新一代高倍币BGV即将登陆交易所
据悉NGK.IO新一代高倍币BGV已与全球前十大交易所进行深度恰谈,预计在不久的将来会完成上线计划.此次BGV的上线战略布局,将进一步扩大BGV生态,赋予BGV更多的便利性和发展空间.除此之外,NGK ...
- Java并发包源码学习系列:同步组件CountDownLatch源码解析
目录 CountDownLatch概述 使用案例与基本思路 类图与基本结构 void await() boolean await(long timeout, TimeUnit unit) void c ...
- mybatisPlus中的模糊查询问题
不能查询中文 记得在数据库的配置中写明编码格式characterEncoding=utf-8
- Navicat premium对数据库的结构同步和数据同步功能
一.在目标数据库新建一个相同的数据库名. 二.工具-->结构同步. 三.填写源数据库和目标数据库. 四.点击比对 五.点击部署 六.点击运行 七.点击关闭.此时源数据库的结构已经同步到目标数据库 ...
- 第39天学习打卡(多线程 Thread Runnable 初始并发问题 Callable )
多线程详解 01线程简介 Process与Thread 程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念. 进程则是执行程序的一次执行过程,它是一个动态的概念.是系统资源分配的 ...
- Django3.0 + nginx + uwsgi 部署
CentOS7.6 下部署Django3.0应用,使用nginx+uwsgi部署: 1. uwsgi部署 pip install uwsgi 在项目的根目录中,新建文件夹 conf, 然后进入conf ...
- 后端程序员之路 26、CAP理论
可能是CAP理论的最好解释 - 西代零零发 - 博客频道 - CSDN.NEThttp://blog.csdn.net/dc_726/article/details/42784237 CAP理论 - ...
- Docker安装Openvas
目录 安装 在本机内运行 在局域网内运行 关闭 参考 安装 ➜ ~ docker search openvas NAME DESCRIPTION STARS OFFICIAL AUTOMATED mi ...
- Excel技巧—开始菜单之格式刷六大功能
转: Excel技巧-开始菜单之格式刷六大功能 点赞再看,养成习惯:千里之行,始于足下. 微信搜索[亦心Excel]关注这个不一样的自媒体人. 本文 GitHub https://github.com ...
- LeetCode-宝石与石头
宝石与石头 LeetCode-771 使用哈希表. 这里使用内置算法库中的map /** * 给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头. * S 中每个字符代表了一种你拥有的 ...