1 简介

单例模式保证了一个类的对象只存在一个,同时维护了一个对其对象的全局共享访问点
image-1689431313612

2 使用场景

  • 某个类对于所有客户端只有一个可用实例。如果该对象已经被创建,则返回已有对象
  • 需要严格地控制全局变量。可以保证一个类只存在一个实例,无法通过其它方式替换缓存的实例
  • 实现计数器类。保证同步数据
  • 日志记录对象。共享的日志读写只能由一个实例操作,保证内容追加的一致性
  • 程序的配置对象。保证共享对象的一致性
  • 数据库连接池。节省数据库连接引起的效率损耗
  • 多线程线程池。方便对线程池的线程进行控制

3 实现方式

3.1 懒汉式

创建对象时不直接创建对象,在需要使用时再创建

// 对象
type singleton struct {
}

var instance *singleton // 全局单例对象

func GetInstance() *singleton {
	// 为空代表还没有线程创建该对象
	if instance == nil {
		instance = &singleton{} // 创建对象
	}
	return instance
}

3.2 饿汉式

直接创建对象,导入包的同时马上创建对象,会持续存储在内存中

// 对象
type singleton struct {
}

var instance *singleton // 全局单例对象

func init() {
	// 直接创建该对象
	if instance == nil {
		instance = &singleton{}
	}
}

func GetInstance() *singleton {
	return instance
}

3.3 双重检查

在懒汉式基础上增加锁,并优化锁操作,保证并发安全且不影响性能

// 对象
type singleton struct {
}

var (
	instance *singleton // 全局单例对象
	mtx      sync.Mutex // 对象锁
)

func GetInstance() *singleton {
	// 判断是否已有对象,已有则不需要加锁判断
	if instance == nil {
		// 加锁判断,防止已经有线程在创建对象了
		mtx.Lock()
		defer mtx.Unlock()

		// 加锁后,此时为空则代表还没有线程创建该对象
		if instance == nil {
			instance = &singleton{} // 创建对象
		}
	}
	return instance
}

3.4 sync.Once

使用Go标准库提供的只执行一次的函数实现。与饿汉式的区别在于,init函数在导包时就把对象生成了,不及时使用就会浪费内存空间,sync.Once可以保证需要使用时,全局只创建对象一次

// 对象
type singleton struct {
}

var (
    instance *singleton // 全局单例对象
    once sync.Once
)    

func GetInstance() *singleton {
	once.Do(func() {
		instance = &singleton{} // 创建对象
	})
	return instance
}

4 优缺点分析

4.1 优点

  • 单例模式可以扩展加入工厂模式中使用
  • 系统内存只存在一个对象,可以避免频繁创建和销毁的消耗,提升性能

4.2 缺点

  • 非抽象形式,可扩展性较低
  • 容易导致共享连接池对象过多,且没有被释放的情况,从而导致连接池溢出;对象长时间没有使用,可能在GC时被认为是垃圾对象而被回收,造成对象丢失