Golang 常见设计模式之单例模式实现方式详解

377 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

定义

在之前做php的时候,经常会用到设计模式,如单例模式、工厂模式等,其实设计模式和语言无关的。单例模式是一种用在特定场景的设计模式,在程序中我们只需要某个类实例化一次即可,保证一个类仅有一个实例,并提供一个获取实例的方法。

特点

  • 单例模式能够保证类只有一个实例;
  • 有方法能够让外部访问到该示例;
  • 外部无法创建实例,因为构造函数为私有函数,外部无法访问,自然也就无法生成实例;

优点

  • 防止其他对象对自己的实例化,确保所有的对象都访问一个实例。
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源。

缺点

  • 不适用于变化的对象,如果同一类型的对象在不同的应用场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
  • 滥用单例将带来一些负面问题,如将数据库连接池对象设计为单例类,可能会导致共享链接池对象的程序过多而出现连接池溢出,如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收。

使用场景

多线程情况下会导致资源访问冲突,需要保证全局唯一的类,比如:配置信息类、连接池类、ID生成器类。

代码实现

懒汉模式

懒汉模式也就是在需要的时候才去创建实例对象,懒汉模式相较于饿汉模式的优点是支持延迟加载,不过懒汉模式不是线程安全的,需要加锁。

//使用结构体代替类
type config struct {

}
//建立所有变量
var cfg *config
//锁对象
var lock sync.Mutex

//加锁保证线程安全
func getInstance() *config {
    lock.Lock()
	defer lock.Unlock()
    
	if cfg == nil {
		cfg := new(config)
		return cfg
	}
	return cfg
}

func main() {
	fmt.Printf("%p\n", getInstance()) //0x119e408
	fmt.Printf("%p\n", getInstance()) //0x119e408
}

此种模式也有缺点,我们给getInstance方法加了一把锁,导致这个函数的并发度很低,如果频繁的用到,那频繁加锁、释放锁就会导致性能下降。

恶汉模式

恶汉模式的实现方式方式比较简单,在类加载的时候,instance静态实例就已经初始化创建好了,所以,instache实例的创建是线程安全的。

var cfg *config

type config struct {

}

func init() {
	cfg = &config{}
}

// NewConfig 提供获取实例的方法
func NewConfig () *config {
	return cfg
}

func main() {
	fmt.Printf("%p\n", NewConfig()) //0x119e428
	fmt.Printf("%p\n", NewConfig()) //0x119e428
}

sync.Once

go 中也提供了 sync.Once 这个方法,来控制只执行一次,该结构体提供了一个Do方法,Do函数里面的函数只有在第一次才会被调用,该方法只会生成一个实例,且也是线程安全的。

type config struct {

}
var cfg *config
var once sync.Once

func GetInstance() *config {
	once.Do(func() {
		cfg = new(config)
	})
	return cfg
}

func main() {
	fmt.Printf("%p\n", GetInstance()) //0x119e428
	fmt.Printf("%p\n", GetInstance()) //0x119e428
}