设计模式 - 单例模式

 

什么是单例模式

一个类只允许创建一个对象(或者实例)

为什么要使用单例

  1. 处理资源的访问冲突
  2. 表示全局唯一类

如何实现一个单例

  • 构造函数需要是private访问权限的,这样才能避免外部通过new创建实例
  • 考虑对象创建时的线程安全问题
  • 考虑是否支持延迟加载
  • 考虑getInstance()性能是否高(是否加锁)

饿汉式

在类加载的时候,静态实例就已经创建并初始化好了,所以是线程安全的。

type singleton1 struct{}

var instance1 *singleton1

func GetInstance1() *singleton1 {
    return instance1
}

func init() {
    instance1 = &singleton1{}
}

懒汉式(带锁)

支持延迟加载,当实例创建后,加锁是非必要的,故有性能瓶颈

type singleton2 struct{}

var instance2 *singleton2
var mu2 sync.Mutex

func GetInstance2() *singleton2 {
	mu2.Lock()
	defer mu2.Unlock()
	if instance2 == nil {
		instance2 = &singleton2{}
	}
	return instance2
}

双重检测

type singleton3 struct{}

var instance3 *singleton3
var mu3 sync.Mutex

func GetInstance3() *singleton3 {
	if instance3 == nil { // <-- Not yet perfect. since it's not fully atomic
		mu3.Lock()
		defer mu3.Unlock()
		if instance3 == nil {
			instance3 = &singleton3{}
		}
	}
	return instance3
}

Go惯用方式(sync.Once)

type singleton struct{}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
	once.Do(func() {
		instance = &singleton{}
	})

	return instance
}

扩展

  1. 如何实现线程内唯一的单例?

    构造一个key为线程ID,value为单例对象的Map,如果存在就返回,否则创建并插入到Map。

  2. 如何实现集群内的单例?

    需要把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。