概念

单例模式 是一种常见的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。

根据初始化的时机不同,单例模式又分为 懒汉式饿汉式下面通过多个实例

懒汉式在第一次使用时创建实例对象,

饿汉式在程序启动时就创建实例对象。

饿汉式

type singleton struct {}

var instance *singleton

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

func GetInstance() *singleton { return instance }

饿汉式线程安全 的,因为在调用GetInstance方法之前 instance已经被实例化好

懒汉式

下面再看下 懒汉式 单例模式,

通过几个版本,带大家彻底理解单例模式

# Version 1
type singleton struct{}

var instance *singleton

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

存在的问题:多线程环境下可能同时进入 if instance == nil 的判断条件,从而创建出多个实例

# Version2
type singleton struct{}

var	mu sync.Mutex
	instance *singleton
)

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

存在问题:加锁机制降低系统并发,造成CPU浪费

# Version 3
type singleton struct{}

var	mu sync.Mutex
	instance *singleton
)

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

该版本解决了

  • 并发调用产生多个实例的问题
  • 版本2,上来就加锁的问题

加锁的目的 就是为了 实例化 的动作由 当个线程来完成,该功能Go内部有现成封装好的供我们使用

# Version 4
// 注意:这里是小写,不可导出
type singleton struct {}

var (
	instance *singleton
  once sync.Once
)

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

在上面的示例中,我们定义了一个名为singleton的结构体类型,该结构体表示单例对象。然后,我们声明一个名为instance的变量来保存单例实例,并使用sync.Once类型的变量once来确保初始化过程只执行一次。

GetInstance函数是一个全局访问点,用于获取单例实例。在该函数内部,我们使用once.Do函数来确保实例只被创建一次。once.Do接受一个函数作为参数,该函数将在第一次调用once.Do时执行。在这个函数中,我们创建了一个新的singleton实例并将其赋值给instance变量。

这样,无论在代码中的哪个位置调用GetInstance函数,都将返回同一个单例实例。

使用单例模式的好处是可以避免创建多个相同的对象,节省了内存和系统资源。同时,它还提供了一个统一的访问点,便于对单例对象进行操作。

需要注意的是,上述实现在多线程环境中是线程安全的。sync.Once的机制能够确保并发访问时只有一个goroutine执行初始化操作。

模式应用场景

单例模式在以下几种场景中是常用的:

  1. 资源共享:当系统中有多个模块或对象需要共享同一个资源时,可以使用单例模式来管理该资源,确保只有一个实例被创建和访问。例如,数据库连接池、线程池等资源的管理。

  2. 配置信息:单例模式可用于管理全局的配置信息,保证系统中的各个模块都使用同一份配置数据。这样可以避免不同模块之间的配置冲突和数据不一致。

  3. 日志记录器:在日志系统中,通常希望只有一个日志记录器实例来负责日志的写入,以避免并发写入导致的问题。单例模式可以确保只有一个日志记录器实例被创建和使用。

  4. 缓存:单例模式可以用于实现缓存系统,确保缓存的一致性和有效性。通过使用单例模式,多个模块可以共享同一个缓存实例,避免重复创建缓存对象。

  5. GUI应用程序中的窗口管理器:在GUI应用程序中,窗口管理器负责管理和控制所有窗口的创建和关闭。使用单例模式可以确保只有一个窗口管理器实例存在,从而保持窗口管理的一致性。

需要注意的是,单例模式的使用应该谨慎,过度使用单例可能导致代码的可测试性可扩展性降低。在设计中,应该根据具体场景和需求来决定是否使用单例模式。