单例模式的推导

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

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

相关推荐
你的人类朋友7 小时前
【Node】单线程的Node.js为什么可以实现多线程?
前端·后端·node.js
iナナ7 小时前
Spring Web MVC入门
java·前端·网络·后端·spring·mvc
CoderYanger8 小时前
优选算法-双指针:2.复写零
java·后端·算法·leetcode·职场和发展
数据知道9 小时前
Go基础:用Go语言操作MongoDB详解
服务器·开发语言·数据库·后端·mongodb·golang·go语言
大鱼七成饱10 小时前
apache POI 万字总结:满足你对报表一切幻想
后端
数据知道12 小时前
Go基础:Go语言应用的各种部署
开发语言·后端·golang·go语言
数据知道13 小时前
Go基础:用Go语言操作MySQL详解
开发语言·数据库·后端·mysql·golang·go语言
种时光的人14 小时前
无状态HTTP的“记忆”方案:Spring Boot中Cookie&Session全栈实战
服务器·spring boot·后端·http
m0_4805026414 小时前
Rust 登堂 之 Cell 和 RefCell(十二)
开发语言·后端·rust
LunarCod15 小时前
Onvif设备端项目框架介绍
后端·嵌入式·c/c++·wsdl·rv1126·onvif