单例模式的推导

2025-07-09

核心思想

保证一个类永远只能有一个对象、且该对象的功能依然能被其他模块使用


单例模式的逻辑推演

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")
	}
}

懒汉式与饿汉式的区别

// 1.懒汉式是在被调用的时候才会去生成对象
func GetInstance1() *singelton1 {
	if atomic.LoadUint32(&initialized) == 1 {
		return instance1
	}

	if instance1 == nil {
		instance1 = new(singelton1)
	}
	return instance1
}

// 2.饿汉式是直接生成对象、调用的时候返回
var instance *singelton = new(singelton)

func GetInstance() *singelton {
	return instance
}

懒汉式单例与并发安全的三种解决方案

方案1:加互斥锁

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

type singelton1 struct{}

var (
	lock        sync.Mutex
	instance1   *singelton1
	initialized uint32
)

// 使用互斥锁解决并发问题
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("懒汉式单例的方法")
}

方案2:使用原子标记

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

type singelton1 struct{}

var (
	lock        sync.Mutex
	instance1   *singelton1
	initialized uint32
)

// 添加原子操作减少锁竞争
func GetInstance1() *singelton1 {
	if atomic.LoadUint32(&initialized) == 1 {
		return instance1
	}
	
	lock.Lock()
	defer lock.Unlock()
	
	if instance1 == nil {
		instance1 = new(singelton1)
		// 设置原子标记
		atomic.StoreUint32(&initialized, 1)
	}
	return instance1
}

方案3:sync.Once

package main

import (
	"fmt"
	"sync"
)

type singelton2 struct{}

var (
	instance2 *singleton2
	once      sync.Once
)

func GetInstance2() *singleton2 {
	once.Do(func() {
		instance2 = &singleton{}
	})
	return instance2
}

func (s *singelton2) Echo2() {
	fmt.Println("懒汉式单例的方法")
}

延申总结

延申1:对于懒汉式单例,高并发情况下可能创建多个对象
      需要考虑线程安全,可以加锁

延申2:如果每次获取单例都加锁,性能不好
      可以使用原子标记减少锁竞争

PREV
gmp模型的作用
NEXT
Hello Halo