首先问大家一个问题,你们面试的时候,面试官有没有问过你们:"你都用过什么设计模式?",我猜多数人的回答会把单例模式,放在第一位。

我:"呃… 我用过单例、工厂、观察者,反向代理,装饰器,哨兵"…. ",

面试官内心OS:"我都没用过这么多...反向代理是什么鬼,这小子背串了吧,不管了先就坡下驴,从头开始问"。

面试官:"用过的挺多哈,那么你能说下单例你都在什么情况下用,顺便在这张纸上实现一下单例吧"。

我:"当需要确保一个类型,只有一个实例时就需要使用单例模式了"。

面试官:"好,那你在纸上实现一下"

十分钟后的我:"不好意思,我们之前项目里都封装好了,我只用过,没有机会实现,所以..."

面试官内心OS:"好吧,这个面试KPI要求得进行三十分钟,这还有小二十分钟呢,随便再问问,就让他回去等信儿吧"

面试卒...

上面是我给大家编的一个场景,如有雷同,请憋住,不要在工位上笑喷~。单例模式虽然简单,不过还是有一些说道儿的,一是应用比较广泛,再来如果不注意容易在多线程环境下造成BUG,今天就给大家简单说下单例模式的应用,以及用Go语言怎么正确地实现单例模式。

单例模式

上面对话里说的没错,单例模式是用来控制类型实例的数量的,当需要确保一个类型只有一个实例时,就需要使用单例模式。

由于要控制数量,那么可想而之只能把实例的访问进行收口,不能谁来了都能 new 一个出来,所以单例模式还会提供一个访问该实例的全局端口,一般都会命名个 GetInstance之类的函数用作实例访问的端口。

又因为在什么时间创建出实例,单例模式又可以分裂出饿汉模式 和 懒汉模式,前者适用于在程序早期初始化时创建已经确定需要加载的类型实例,比如项目的数据库实例。后者其实就是延迟加载的模式,适合程序执行过程中条件成立才创建加载的类型实例。

下面我们用 Go 代码把这两种单例模式实现一下。

饿汉模式

这个模式用 Go 语言实现时,借助 Go 的init函数来实现特别方便

如果你想了解 Go init 函数的方方面面,可以看以前的老文章Go语言init函数你必须记住的六个特征

下面用单例模式返回数据库连接实例,相信你们在项目里都见过类似代码。

package dao
// 饿汉式单例
// 注意定义非导出类型
type  databaseConn struct{...
}var dbConn *databaseConnfunc init() {dbConn = &databaseConn{}
}// GetInstance 获取实例
func Db() *databaseConn {return dbConn
}

这里初始化数据库的细节咱们就不多费文笔了,实际情况肯定是从配置中心加载下来数据库连接配置再实例化数据库的连接对象。这里有人可能会有个问题,你这一个程序进程就只有一个数据连接实例,那这么多请求都用一个数据库连接行吗?

诶,这个是对数据库连接的抽象呀,这个实例会维护一个连接池,那里才是真正去请求数据库用的连接。是不是有点晕,有点晕去看看你们项目里这块的代码。一般会看到初始化实例时,让你设置最大连接数、闲置连接数和存活时间这样的连接池配置。

懒汉模式

懒汉模式--通俗点说就是延迟加载,不过这块特别注意,要考虑并发环境下,你的判断实例是否已经创建时,是不是用的当前读。在一些教设计模式的教程里,一般这种情况下会举一个例子--用 Java 双重锁实现线程安全的单例模式,双重锁指的是volatilesynchronized

class Singleton {private volatile static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if(instance==null) {synchronized (Singleton.class) {if(instance==null)instance = new Singleton();}}return instance;}
}

上面这个例子里,如果不给instance属性加上 volatile修饰符,那么虽说创建的过程已经用synchronized给类加了锁,但是有可能读到的instance是线程缓存是滞后的,有可能属性此时已经被其他线程初始化了,所以就必须加上volatile保证当前读(读主存里属性的状态)。

那么 Go 里边没有volatile这种机制,我们该怎么办呢?聪明的你一定能想得出,我们定义一个实例的状态变量,然后用原子操作atomic.Loadatomic.Store去读写这个状态变量,不就是实现了吗?像下面这样:

如果 Go 原子操作你还不熟,请看老文章Golang 五种原子性操作的用法详解

import "sync"
import "sync/atomic"var initialized uint32type singleton struct {...
}func GetInstance() *singleton {if atomic.LoadUInt32(&initialized) == 1 {  // 原子操作 return instance}mu.Lock()defer mu.Unlock()if initialized == 0 {instance = &singleton{}atomic.StoreUint32(&initialized, 1)}return instance
}

确实,相当于把上面 Java 的例子翻译成用 Go 实现了,不过还有另外一种更Go native 的写法,比这种写法更简练。如果用 Go 更惯用的写法,我们可以借助其sync库中自带的并发同步原语Once来实现:

package singletonimport ("sync"
)type singleton struct {}var instance *singleton
var once sync.Oncefunc GetInstance() *singleton {once.Do(func() {instance = &singleton{}})return instance
}

关于sync.One 的使用和其实现原理…我发现我的Go 并发编程系列里没单独写Once这个原语,可能是觉得太简单了吧,后期抽空补上吧… 不过只是原理分析没写,怎么应用在Go语言sync包的应用详解里也能找到。

总结

这篇文章其实是把单例模式的应用,和Go的单例模式版本怎么实现给大家说了一下,现在教程大部分都是用 Java 讲设计模式的,虽然我们可以直接翻译,不过有的时候 Go 有些更native 的实现方式,让实现更简约一些。之前还写了一个观察者模式的文章,拒绝Go代码臃肿,其实在这几块可以用下观察者模式。

资料下载

点击下方卡片关注公众号,发送特定关键字获取对应精品资料!

  • 回复「电子书」,获取入门、进阶 Go 语言必看书籍。

  • 回复「视频」,获取价值 5000 大洋的视频资料,内含实战项目(不外传)!

  • 回复「路线」,获取最新版 Go 知识图谱及学习、成长路线图。

  • 回复「面试题」,获取四哥精编的 Go 语言面试题,含解析。

  • 回复「后台」,获取后台开发必看 10 本书籍。

对了,看完文章,记得点击下方的卡片。关注我哦~ 

最简单的单例模式,Go版本的实现你写对了吗?相关推荐

  1. 简单记录一下fabric版本1.4的环境搭建,

    简单记录一下fabric版本1.4的环境搭建,运行环境为Ubuntu18.04,其中一些内容是根据官方文档整理的,如有错误欢迎批评指正. 本文只介绍最简单的环境搭建方法,具体的环境搭建解析在这里深入解 ...

  2. qt创建简单的单例模式

    引言 本文章只适用于简单的单例模式.由于本人也没有深究过.下面直接上代码. 示例 单例模式,就是只能创建一个该类的对象,因为这样其构造函数是私有的,外部只能通过接口获得创建的该类唯一的对象. #ifn ...

  3. 直播电商软件开发,简单的单例模式

    直播电商软件开发,简单的单例模式相关的代码 public class Instance {private static Instance instance;private Instance() {}p ...

  4. 【Linux网络编程学习】使用socket实现简单服务器——多进程多线程版本

    此为牛客Linux C++课程和黑马Linux系统编程笔记. 1. 多进程版 1.1 思路 大体思路与上一篇的单进程版服务器–客户端类似,都是遵循下图: 多进程版本有以下几点需要注意: 由于TCP是点 ...

  5. C语言实现简单的单例模式

    单例模式就是在程序运行过程中只存在此"对象"的一个"实例",C语言实现单例模式就是简单的运用static全局变量. 我们的学号在教务系统中无论在任何地点进行任何 ...

  6. glibc版本查看_[译] 写一个简单的内存分配器(替换glibc中的malloc函数)

    本文介绍如何用c语言实现一个简单的内存分配器,可替换glibc中的 malloc(), calloc(), realloc(), free(). 这是一篇入门级别的文章,所以不会介绍所有的细节. 代码 ...

  7. 面试积累(简单的单例模式)

    如果我们在做项目时,一个对象在很多地方都要频繁的使用,如果每用一次就new一个对象,这样做无疑会大大的浪费资源,降低程序的性能,这时,我们可以将这个创建对象的这个过程提取出来,做成一个单例模式.常见的 ...

  8. 简单的单例模式其实不简单

    小伙们好,我是jack xu,今天跟大家讲一个老生常谈的话题,单例模式是最常用到的设计模式之一,熟悉设计模式的朋友对单例模式都不会陌生.网上的文章也很多,但是参差不齐,良莠不齐,要么说的不到点子上,要 ...

  9. Linux查容器编码,自己动手写docker笔记(4)构造简单实现run命令版本的容器

    原书代码 https://github.com/xianlubird/mydocker.git #code-3.1 Linux Proc Linux 下的/proc 文件系统是由内核提供,它其实不是一 ...

最新文章

  1. 怎样提高WebService的性能
  2. Facebook发布Detectron2,下一个万星目标检测新框架
  3. android如何根据机顶盒区分用户,Android 上手机跟机顶盒应用开发的区别
  4. 【Ansible 文档】【译文】模式
  5. 必须要用发展的眼光来看诺基亚和微软的合作
  6. 两种方式:mysql查看正在执行的sql语句
  7. containers文件夹可以删除吗_电脑进行C盘清理,appdata文件夹可以删除吗?
  8. jQuery异步加载数据并添加事件示例
  9. win7制作ntp服务器,如何将Win7作为NTP服务器
  10. MySql 你知道如何正确的取随机数据吗 ?
  11. 三星Galaxy M52 5G通过认证:两个版本 搭载骁龙778G芯片
  12. 微处理器OpenRisc、SPARC、RISC-V架构
  13. 7.大数据架构详解:从数据获取到深度学习 --- 批处理技术
  14. 字符串函数---atof()函数详解及实现
  15. 从零开始学Qt(一)前言
  16. SAP计划策略组详细介绍
  17. Pandas的panel结构
  18. 基于微信小程序的面包店在线服务系统
  19. 无敌破坏王-高清在线观看
  20. HTTP 错误 404.17 - Not Found 请求的内容似乎是脚本,因而将无法由静态文件处理程序来处理。...

热门文章

  1. 什么是纯虚函数?什么是抽象类?
  2. CnPeng说:“快应用”的慢节奏
  3. 近十年量化交易领域最重要的十本参考书是哪些
  4. 重装Ubuntu(Linux)后快速搭建工作环境(深度学习环境)
  5. 面试最常问的设计模式
  6. The bussiness flow between DWM and ECC
  7. 国内外电商平台反爬虫机制报告
  8. 一篇让我感动的文章: 五年考研
  9. 单细胞转录组文章复现系列(一)——seurat
  10. MLY -- 8.Establish a single-number evaluation metric for your team to optimize