1、核心思想
保证一个类永远只能有一个对象、且该对象的功能依然能被其他模块使用
2、单例模式的逻辑推演
什么是单例模式呢、其实开篇第一句话已经讲的很清楚了、但是对于某些同学来说可能有点难理解、要如何保证一个类永远只能有一个对象、但是这个对象的功能又能被其他模块使用呢?
只要你遵循下面例子中的三个要点就可以了
go
package main
import "fmt"
/*
三个要点:
1.某个类只能有一个实例
2.它/须自行创建这个实例
3.它须自行向整个系统提供这个实例。保证一个类永远只能有一个对象
*/
// 1.如果这个类是首字母大写的话,这个类就可以被外界用来创建对象、就不符合第二个要点、可能会创建多个对象、所以这个类应该是非共有访问、首字母需要小写
type singelton struct {
}
// 2.还要有一个指针指向这个唯一对象、但是这个指针永远不能改变方法、这个其实是为了符合第一个要点
// golang中没有常指针概念、因此只能通过将这个指针私有化不让外部模块访问、所以指针也是小写的
var instance *singelton = new(singelton)
// 3.如果全部私有化、那么外部模块就无法访问到这个对象、所以需要对外提供一个方法来获取这个对象、很显然这个符合第三个要点、保证它可能向整个系统提供
func GetInstance() *singelton {
return instance
}
//4.这个时候我们就可以为对象来创建方法了
func (s *singelton) Echo() {
fmt.Println("单例的方法")
}
func main() {
s1 := GetInstance()
s1.Echo()
s2 := GetInstance()
if s1 == s2 {
fmt.Println("s1 == s2")
}
}
//输出结果是
//单例的方法
//s1 == s2
//当我们第二次去调用GetInstance()的时候、实际上返回的还是刚开始就创建好的、感兴趣的小伙伴可以自己试一下
3、懒汉式与饿汉式的区别
单例模式分为两种、第一种是饿汉式单例、第二种是懒汉式单例、那么这两种有什么区别呢?
直接告诉你答案吧
饿汉式单例模式是一开始就创建好一个对象、你每次调用的时候用的都是那个已经创建好的
懒汉式单例模式呢、是当你调用的时候才会开始创建、然后其他人再调用的时候用第一个人创建的
看一下下面的例子加深一下理解吧
go
1.懒汉式是在被调用的时候才会去生成对象
func GetInstance1() *singelton1 {
if instance1 == nil {
instance1 = new(singelton1)
}
return instance1
}
2.饿汉式是直接生成对象、调用的时候返回
var instance *singelton = new(singelton)
func GetInstance() *singelton {
return instance
}
4、懒汉式单例与并发安全
懒汉式单例有一个弊端、就是如果同时有多个用户同时获取单例、他们得到的结果都是还没有创建、结果每个人都创建了一个、这样肯定是不对的、那么有什么办法可以避免这种现象的发生呢
我们有三种解决办法
1、加互斥锁
既然多个用户可能同时创建、那我在创建的时候锁起来只运行一个人创建不就好了
go
package main
import (
"fmt"
"sync"
"sync/atomic"
)
type singelton1 struct {
}
var (
lock sync.Mutex
instance1 *singelton1
initialized uint32
)
//1.第一种方式我们使用互斥锁来解决并发的问题、每次调用GetInstance1都会加锁、并发情况下goroutine会在锁上排队、产生开销
func GetInstance1() *singelton1 {
if atomic.LoadUint32(&initialized) == 1 {
return instance1
}
//如果不存在、加锁申请
lock.Lock()
defer lock.Unlock()
if instance1 == nil {
instance1 = new(singelton1)
}
return instance1
}
func (s *singelton1) Echo1() {
fmt.Println("懒汉式单例的方法")
}
func main() {
s1 := GetInstance1()
s1.Echo()
s2 := GetInstance1()
if s1 == s2 {
fmt.Println("s1 == s2")
}
}
// 延申1.对于懒汉式单例,如果高并发的情况下有可能创建多个对象、需要考虑线程安全、可以加个锁
2、使用原子标记
go
package main
import (
"fmt"
"sync"
"sync/atomic"
)
type singelton1 struct {
}
var (
lock sync.Mutex
instance1 *singelton1
initialized uint32
)
//1.第二种我们在原有的互斥锁基础上添加原子操作、在外层过滤已经初始化的情况、减少锁竞争
func GetInstance1() *singelton1 {
if atomic.LoadUint32(&initialized) == 1 {
return instance1
}
//如果不存在、加锁申请
lock.Lock()
defer lock.Unlock()
if instance1 == nil {
instance1 = new(singelton1)
// 设计标记为1
atomic.StoreUint32(&initialized, 1)
}
return instance1
}
func (s *singelton1) Echo1() {
fmt.Println("懒汉式单例的方法")
}
func main() {
s1 := GetInstance1()
s1.Echo()
s2 := GetInstance1()
if s1 == s2 {
fmt.Println("s1 == s2")
}
}
// 延申2.如果每次获取这个单例的时候都加个锁、会比较慢、性能不好、可以使用原子标记
3、sync.Once
go
package main
import (
"fmt"
"sync"
"sync/atomic"
)
type singelton2 struct {
}
var (
instance2 *singleton2
once sync.Once
)
func GetInstance2() *singleton2 {
once.Do(func() {
instance2 = &singleton{}
})
return instance
}
func (s *singelton2) Echo2() {
fmt.Println("懒汉式单例的方法")
}
func main() {
s1 := GetInstance1()
s1.Echo()
s2 := GetInstance1()
if s1 == s2 {
fmt.Println("s1 == s2")
}
}
//sync包是Go语言标准库中的一个类型,它提供了一种机制来确保某个操作只执行一次、既简单又好用
//引申:有兴趣的同学可以自行查询sync.once的实现原理、是通过atomic和mutex组合实现
好了、我们今天的分享就到这里、如果小伙伴们有任何的意见或者建议、欢迎评论与我分享!!