单例模式的推导

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组合实现

好了、我们今天的分享就到这里、如果小伙伴们有任何的意见或者建议、欢迎评论与我分享!!

相关推荐
bug攻城狮6 小时前
Spring Boot 2.6+ 整合 PageHelper 启动报错:循环依赖解决方案全解析
java·spring boot·后端
IT_陈寒7 小时前
Vue 3.4性能优化实战:5个鲜为人知的Composition API技巧让打包体积减少40%
前端·人工智能·后端
大厂码农老A7 小时前
我带的外包兄弟放弃大厂转正,薪资翻倍入职字节
java·后端·面试
武子康8 小时前
大数据-136 - ClickHouse 集群 表引擎详解 选型实战:TinyLog/Log/StripeLog/Memory/Merge
大数据·分布式·后端
Somehow0078 小时前
从Binlog到消息队列:构建可靠的本地数据同步管道(macOS本地部署Canal & RocketMQ并打通全流程)
后端·架构
ai安歌8 小时前
【Rust编程:从新手到大师】Rust概述
开发语言·后端·rust
用户6120414922138 小时前
C语言做的智能家居控制模拟系统
c语言·后端·敏捷开发
豆苗学前端8 小时前
10分钟带你入门websocket,并实现一个在线多人聊天室
前端·javascript·后端
风霜不见闲沉月8 小时前
rust更新后编译的exe文件执行报错
开发语言·后端·rust
稚辉君.MCA_P8_Java8 小时前
Bash 括号:()、{}、[]、$()、$(() )、${}、[[]] 到底有什么区别?
开发语言·jvm·后端·容器·bash