单例模式

651 阅读2分钟

设计模式是经过验证,用于解决在特定环境下、重复出现、特定问题的解决方案。 github github.com/zexho994/go…

背景

在一个系统的运行期间,某个类只需要一个实例就好了,同时提供一个访问它的全局访问点。

单例模式就是解决这两个问题的:

  1. 保证类只有一个实例

    为什么要控制只有一个实例?最常见的原因就是控制某些共享资源(例如数据库或者文件)的访问权限,运作逻辑是这样的,如果你创建一个对象,发现这个对象已经创建过,就将已创建的对象返回给你。

  2. 提供一个全局访问节点

任何方式实现单例模式,都要实现两个点:

  1. 隐藏构造方法,将其设为私有。因为构造方法总是返回一个新的对象。
  2. 使用一个方法代替构造方法,该方法内部逻辑可以总结为
    • 存在一个类变量保存类实例
    • 如果该变量未初始化,调用构造方法创建新实例。
    • 如果该变量已经初始化,返回该变量。

实现单例常用的方式有两种,DLC(双段锁)init函数

DLC

代码

type signle struct {
}

var lock = sync.Mutex
var instance *signle

func getInstance() *signle {
	if instance == nil { 
		lock.Lock()
		defer lock.Unlock()
		if instance == nil {
			instance = &signle{}
		}
	}
	return instance
}

有两个会经常问到的问题

  1. 为什么判断两次 instance == nil
  2. 为什么上锁

为什么上锁?

有一种情况,就是在instance还未初始化的时候,同时进入了2个线程,线程A执行到lock.Lock()处中断,开始执行线程B,线程B执行到instance = &signle{}处再次中断(p1位置),切换回线程A,如果此时没有锁,那么A可能会执行instance = &signle{} 然后线程B切回来后会在执行一次 instance = $signle{} ,这就发生并发冲突了。而有了lock之后,在p1位置时候后,切换会A,A由于拿不到锁,那么就进入不到里面的步骤了,得B释放锁之后才能继续执行。

为什么上两次判空?

先说第二次if判断,接着上面的步骤,在B初始完结束后,释放锁,这时候A再拿到锁,如果这时候没有 if singleInstance == nil 的逻辑,同样会导致A再执行一边初始化。

回到第一次if判断,这一次的作用是提高性能,因为只有在刚开始的时候才会出现instance == nil的情况,在进行初始化之后,所有的请求都是无法通过第一个if的,这样可以避免后续的请求去lock等操作。

Once( )

var once sync.Once
var instance *single

func getInstance() *single {
	if instance == nil {
		once.Do(func() {
			instance = &single{}
		})
	}
	return instance
}

sync.Once 保证代码仅会执行一次,也就代表instance = &single{}只会执行一次