go解锁设计模式之单例模式(有个小坑要注意一下啊)

前言

哈喽,你们好,我是asong,这是个人第16篇原创文章,感谢各位的关注。今天给你们分享设计模式之单例模式,并使用go语言实现。熟悉java的同窗对单例模式必定不陌生,单例模式,是一种很常见的软件设计模式,在他的核心结构中只包含一个被称为单例的特殊类。经过单例模式能够保证系统中一个类只有一个实例且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。下面咱们就一块儿来看一看怎么使用go实现单例模式,这里有一个小坑,必定要注意一下,结尾告诉你哦~~~

什么是单例模式

单例模式确保某一个类只有一个实例。为何要确保一个类只有一个实例?有何时才须要用到单例模式呢?听起来一个类只有一个实例好像没什么用呢! 那咱们来举个例子。好比咱们的APP中有一个类用来保存运行时全局的一些状态信息,若是这个类实现不是单例的,那么App里面的组件可以随意的生成多个类用来保存本身的状态,等于你们各玩各的,那这个全局的状态信息就成了笑话了。而若是把这个类实现成单例的,那么无论App的哪一个组件获取到的都是同一个对象(好比Application类,除了多进程的状况下)。java

image

饿汉模式

这里咱们使用三种方式实现饿汉模式。先说一下什么是懒汉模式吧,从懒汉这两个字,咱们就能知道,这我的很懒,因此他不可能在未使用实例时就建立了对象,他确定会在使用时才会建立实例,这个好处的就在于,只有在使用的时候才会建立该实例。下面咱们一块儿来看看他的实现:git

  • 不加锁
package one

type singleton struct {

}

var  instance *singleton
func GetInstance() *singleton {
    if instance == nil{
        instance = new(singleton)
    }
    return instance
}

这种方法是会存在线程安全问题的,在高并发的时候会有多个线程同时掉这个方法,那么都会检测instance为nil,这样就会致使建立多个对象,因此这种方法是不推荐的,我再来看第二种写法。github

  • 整个方法加锁
type singleton struct {

}

var instance *singleton
var lock sync.Mutex

func GetInstance() *singleton {
    lock.Lock()
    defer lock.Unlock()
    if instance == nil{
        instance = new(singleton)
    }
    return instance
}

这里对整个方法进行了加锁,这种能够解决并发安全的问题,可是效率就会降下来,每个对象建立时都是进行加锁解锁,这样就拖慢了速度,因此不推荐这种写法。golang

  • 建立方法时进行锁定
type singleton struct {

}

var instance *singleton
var lock sync.Mutex

func GetInstance() *singleton {
    if instance == nil{
        lock.Lock()
        instance = new(singleton)
        lock.Unlock()
    }
    return instance
}

这种方法也是线程不安全的,虽然咱们加了锁,多个线程一样会致使建立多个实例,因此这种方式也不是推荐的。因此就有了下面的双重检索机制面试

  • 双重检锁
type singleton struct {
    
}

var instance *singleton
var lock sync.Mutex

func GetInstance() *singleton {
    if instance == nil{
        lock.Lock()
        if instance == nil{
            instance = new(singleton)
        }
        lock.Unlock()
    }
    return instance
}

这里在上面的代码作了改进,只有当对象未初始化的时候,才会有加锁和减锁的操做。可是又出现了另外一个问题:每一次访问都要检查两次,为了解决这个问题,咱们可使用golang标准包中的方法进行原子性操做。设计模式

  • 原子操做实现
type singleton struct {
    
}

var instance *singleton
var once sync.Once
func GetInstance() *singleton {
    once.Do(func() {
        instance = new(singleton)
    })
    return instance
}

这里使用了sync.OnceDo方法能够实如今程序运行过程当中只运行一次其中的回调,这样就能够只建立了一个对象,这种方法是推荐的~~~。安全

饿汉模式

有懒汉模式,固然还要有饿汉模式啦,看了懒汉的模式,饿汉模式咱们很好解释了,由于他饿呀,因此很着急的就建立了实例,不用等到使用时才建立,这样咱们每次调用获取接口将不会从新建立新的对象,而是直接返回以前建立的对象。比较适用于:若是某个单例使用的次数少,而且建立单例消息的资源比较多,那么就须要实现单例的按需建立,这个时候懒汉模式就是一个不错的选择。不过也有缺点,饿汉模式将在包加载的时候就会建立单例对象,当程序中用不到该对象时,浪费了一部分空间,可是相对于懒汉模式,不须要进行了加锁操做,会更安全,可是会减慢启动速度。架构

下面咱们一块儿来看看go实现饿汉模式:并发

type singleton struct {

}

var instance = new(singleton)

func GetInstance()  *singleton{
    return instance
}

或者
type singleton struct {

}

var instance *singleton

func init()  {
    instance = new(singleton)
}

func GetInstance()  *singleton{
    return instance
}

这两种方法均可以,第一种咱们采用建立一个全局变量的方式来实现,第二种咱们使用init包加载的时候建立实例,这里两个均可以,不过根据golang的执行顺序,全局变量的初始化函数会比包的init函数先执行,没有特别的差距。框架

小坑

还记得我开头说的一句话,go语言中使用单例模式有一个小坑,若是不注意,就会致使咱们的单例模式没有用,能够观察一下我写的代码,除了GetInstance方法外其余都使用的小写字母开头,知道这是为何吗?

golang中根据首字母的大小写来肯定能够访问的权限。不管是方法名、常量、变量名仍是结构体的名称,若是首字母大写,则能够被其余的包访问;若是首字母小写,则只能在本包中使用。能够简单的理解成,首字母大写是公有的,首字母小写是私有的。这里type singleton struct {咱们若是使用大写,那么咱们写的这些方法就没有意义了,其余包能够经过s := &singleton{}建立多个实例,单例模式就显得很没有意义了,因此这里必定要注意一下哦~~~

总结

这一篇就到此结束了,这里讲解了23种模式中最简单的单例模式,虽然他很简单,可是越简单的越容易犯错的呦,因此必定要细心对待每一件事情的呦~~

好啦,这一篇就到此结束了,个人代码已上传github:https://github.com/asong2020/...

欢迎star

结尾给你们发一个小福利吧,最近我在看[微服务架构设计模式]这一本书,讲的很好,本身也收集了一本PDF,有须要的小伙能够到自行下载。获取方式:关注公众号:[Golang梦工厂],后台回复:[微服务],便可获取。

我翻译了一份GIN中文文档,会按期进行维护,有须要的小伙伴后台回复[gin]便可下载。

我是asong,一名普普统统的程序猿,让我一块儿慢慢变强吧。我本身建了一个golang交流群,有须要的小伙伴加我vx,我拉你入群。欢迎各位的关注,咱们下期见~~~

推荐往期文章:

相关文章
相关标签/搜索