在研读业务代码时,我发现自定义类型会以“懒汉式-非线程安全”和“饿汉式”进行实例化,因此对单例模式产生兴趣,它是什么?原理是怎样的?解决了什么样的问题?应用场景如何?优缺点是什么?
单例设计模式(Singleton Design Pattern): 一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
单例模式是最简单的设计模式之一,它提供了创建对象的最佳方式。
这种模式涉及到一个单一的结构体,该结构体负责创建自己的对象,同时确保只有单个对象被创建。即多次创建一个结构体的对象,得到的对象的存储地址永远与第一次创建对象的存储地址相同。
goroutine1:
{
file1 = new(File)
file1.write("abc")
}
goroutine2:
{
file2 = new(File)
file2.write("defh")
}
多协程对同一文件的实例进行写操作时,就有可能存在写入的数据互相覆盖的情况。
从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类。
1.需要频繁实例化然后销毁的对象。
2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3.有状态的工具类对象。
4.频繁访问数据库或文件的对象。
单例模式是最简单的设计模式之一,它提供了创建对象的最佳方式。
这种模式涉及到一个单一的结构体,该结构体负责创建自己的对象,同时确保只有单个对象被创建。即多次创建一个结构体的对象,得到的对象的存储地址永远与第一次创建对象的存储地址相同。
直接创建对象,线程安全。
缺点
package singleton
type School struct{}
var (
instance *School
)
func init(){
instance = new(School)
}
func GetInstance() *School {
return instance
}
优点是支持延迟加载
缺点是通过懒汉式-非线程安全方式创建单例,在并发下可能会多次创建
package singleton
type School struct{}
var (
instance *School
)
func GetInstance() *School {
if instance == nil {
instance = new(School)
}
return instance
}
在非线程安全的基础上,利用Sync.Mutex进行加锁,保证线程安全,
但由于每次调用该方法都进行了加锁操作,在性能上相对不高效
package singleton
type School struct {}
var (
instance *School
lock *sync.Mutex = &sync.Mutex{}
)
func GetInstance() *School {
lock.Lock()
defer lock.Unlock()
if instance == nil {
instance = new(School)
}
return instance
}
在懒汉式(线程安全)的基础上再进行忧化,通过判断来减少加锁的操作。保证线程安全同时不影响性能。这种实现方式解决了懒汉式并发度低的问题。
设计思路:第一次判断不加锁,第二次加锁保证线程安全,一旦对象建立后,获取对象就不用加锁了
package singleton
var (
instance *School
lock sync.Mutex
)
func GetInstance() *School {
if instance == nil {
lock.Lock() //第一次判断不加锁,第二次加锁保证线程安全,一旦对象建立后,获取对象就不用加锁了
if instance == nil {
instance = new(School)
}
lock.Unlock()
}
return instance
}
利用 sync.Once 方法Do,确保Do中的方法只被执行一次的特性,创建单个结构体实例。
使用Do方法也巧妙的保证了并发线程安全。
package singleton
var (
instance *School
once sync.Once
)
func GetInstance() *School {
once.Do(func() {
instance = new(School)
})
return instance
}
其中Once源码利用atomic操作实现f函数只执行一次
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 1 { 判断是否执行过该方法,如果执行过则不执行
return
}
// Slow-path.
o.m.Lock()
defer o.m.Unlock() 进行加锁,再做一次判断,如果没有执行,则进行标志已经执行并调用该方法
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
package main
import (
"fmt"
"strconv"
"sync"
)
type School struct {
name string
id int
}
var (
instance *School
once sync.Once
wg sync.WaitGroup
)
func GetInstance(name string, id int) *School {
once.Do(func() {
fmt.Println("------------init-----------")
instance = new(School)
})
instance.name = name
instance.id = id
return instance
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1)
go func(seq int) {
defer wg.Done()
instance = GetInstance("xiaobai"+strconv.Itoa(seq), 25)
fmt.Printf("gonum: %s, address: %p, name: %s, id: %d\n",
strconv.Itoa(seq), instance, instance.name, instance.id)
}(i)
}
wg.Wait()
fmt.Println("end!")
}
本文go设计模式实现单例模式,借鉴于Java设计模式,存在未考虑完善的地方:
1 https://time.geekbang.org/column/article/194035
2 https://blog.csdn.net/TCatTime/article/details/106882600
3 https://blog.csdn.net/qq_37703616/article/details/81989889
4 https://github.com/tmrts/go-patterns/blob/master/creational/singleton.md