单例模式的推导

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

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

相关推荐
就是帅我不改2 小时前
揭秘Netty高性能HTTP客户端:NIO编程的艺术与实践
后端·面试·github
Ray662 小时前
SugLucene索引构建
后端
舒一笑2 小时前
Saga分布式事务框架执行逻辑
后端·程序员·设计
Emma歌小白3 小时前
完整后台模块模板
后端
得物技术3 小时前
MySQL单表为何别超2000万行?揭秘B+树与16KB页的生死博弈|得物技术
数据库·后端·mysql
Emma歌小白3 小时前
配套后端(Node.js + Express + SQLite)
后端
isysc13 小时前
面了一个校招生,竟然说我是老古董
java·后端·面试
uhakadotcom3 小时前
静态代码检测技术入门:Python 的 Tree-sitter 技术详解与示例教程
后端·面试·github
幂简集成explinks4 小时前
e签宝签署API更新实战:新增 signType 与 FDA 合规参数配置
后端·设计模式·开源