Golang设计模式(二):单例模式

单例模式

Singletion Pattern:确保某一个类只有一个实例,而且自动执行实例化并向整个系统提供这个实例,这个类称为单例类,

使用场景

  1. 全局监控:用于全局监控统计模块,确保整个系统内监控实例的唯一性。
  2. 资源密集型类:适用于创建时消耗大量资源的类,如协程池、数据库连接池、第三方客户端等,以减少资源消耗。
  3. 配置信息共享:管理全局配置信息,确保多个模块间配置的一致性和共享。
  4. 数据库连接管理:作为数据库连接池的管理者,避免创建过多连接,提高资源利用率。
  5. 日志系统:实现线程安全的日志记录器,供多线程环境下的所有线程使用。
  6. 硬件设备管理:统一管理打印机、扫描仪等硬件设备,避免重复初始化。
  7. 资源分配:优化内存、文件句柄等有限资源的分配和管理。
  8. 服务定位:作为服务定位器,提供对系统中特定服务的全局访问。
  9. 数据缓存:实现全局缓存机制,确保数据一致性和多模块间的共享。
  10. 权限验证:构建全局权限检查器,统一权限验证流程。
  11. 状态同步:管理全局状态,如用户会话,确保状态在系统间的同步和共享。
  12. 系统组件跟踪:创建全局注册表,跟踪和管理系统内所有组件的状态。

Golang中实现单例模式的两种形式:

饿汉式与懒汉式

饿汉式

在程序启动时就创建单例对象,以确保在程序运行期间只有一个实例

饿汉式编写步骤:

  1. 私有化单例类:确保单例类和其构造函数是私有的(不可导出的),以防止外部直接实例化,从而维护单例的一致性。
  2. 预初始化实例:在程序启动时,预先创建并初始化单例对象的全局唯一实例,该实例将作为后续操作中的“单例”。
  3. 提供公共访问点:通过一个可导出的(公开的)方法 GetXXX() 向外部提供对单例对象的访问,确保全局只通过这一途径获取单例实例。

代码实例

// singleHungry 是一个单例模式的实现,用于保证全局仅有一个实例。
var s *singleHungry

// init 初始化函数,在程序启动时执行,确保 singleHungry 实例的创建。
func init(){
	s = newsingleHungry()
}

// singleHungry 结构体定义。
type singleHungry struct {
}

// newsingleHungry 创建并返回一个 singleHungry 的新实例。
func newsingleHungry() *singleHungry {
	return &singleHungry{}
}

// Work 实现了 Instance 接口的 Work 方法,打印工作信息。
func (s *singleHungry) Work() {
	fmt.Println("single hungry is working....")
}

// Instance 接口定义,规定了 Work 方法。
type Instance interface {
	Work()
}

// GetSingleHungry 返回 singleHungry 的全局实例。
func GetSingleHungry()Instance{
 return s
}

懒汉式

只有在需要使用的是由再执行单例的初始化工作

懒汉模式在多线程环境中面临挑战,需要利用互斥锁,以确保初始化过程仅执行一次。即便如此,仍存在两个线程几乎同时竞争锁的风险。假设一者成功获取锁并执行初始化,另一者则处于等待状态。当初始化完毕且锁被释放后,等待的线程随即取得锁并可能重新执行初始化,从而违背了单一初始化的原则。为解决这一问题,引入双重检查机制至关重要:即在获取锁之后,再次验证是否已完成初始化,以避免不必要的重复操作。

懒汉式编写步骤:

  1. 私有化单例类:确保单例类及其构造函数是不可导出的,防止外部直接创建实例。
  2. 延迟初始化单例变量:在类内部声明一个私有的单例变量,但不立即实例化,以实现延迟初始化。
  3. 提供公共获取方法:公开一个方法,如 GetInstance(),供外部获取单例对象的实例。
  4. 使用互斥锁保护初始化:在 GetInstance() 方法中使用互斥锁(sync.Mutex)来避免多个goroutine同时初始化单例。
  5. 双重检查锁定(Double-Check Locking):在获取互斥锁之后,进行第二次检查,确保在锁定期间单例未被其他goroutine初始化。这样可以避免不必要的初始化操作,确保单例只被初始化一次。
  6. 初始化后释放锁:一旦单例实例化完成,释放互斥锁,允许等待的goroutine继续执行。

代码实例

// singleLazy 实现了 SingleLazyInstance 接口,保证全局仅有一个实例化的对象
var(
	l *singleLazy // 全局单例对象
	mu sync.Mutex // 互斥锁,用于线程安全的单例创建
) 

type singleLazy struct{} // 空结构体,实际的单例对象类型

// Work 打印消息表明单例对象正在工作
func (s *singleLazy) Work() {
	fmt.Println("single lazy work...")
}

// SingleLazyInstance 定义了单例模式的接口
type SingleLazyInstance interface {
	Work()
}

// newsingleLazy 实例化一个新的 singleLazy 对象
func newsingleLazy() *singleLazy{
	return &singleLazy{}
}

// GetSingleLazy 返回 singleLazy 的全局唯一实例
// 如果实例尚未创建,则创建一个新的实例并返回。
// 该方法保证了线程安全的单例模式实现。
func GetSingleLazy() SingleLazyInstance{
	if l !=nil{
		fmt.Println("single lazy is already created>>")
		return l
	}
	mu.Lock() // 加锁确保线程安全
	defer mu.Unlock() // 确保函数退出时解锁
	if l!=nil{
		fmt.Println("single lazy is created by other thread>>")
		return l // 如果已经在其他线程中创建,直接返回该实例
	}
	l=newsingleLazy() // 在此线程中首次创建单例对象	
	return l
}	

golang的sync.Once 实现懒汉单例

它提供了一种机制来保证初始化操作只执行一次。这种机制特别适用于单例模式的实现,以及任何需要确保某个操作只执行一次的场景。

sync.Once 包含一个名为 Do 的方法,该方法接受一个 func() 类型的参数,即一个无参数、无返回值的函数。当 Do 方法被调用时,如果还没有进行过初始化(即 sync.Once 实例的内部状态为未初始化),它将执行提供的函数,并在执行完毕后将内部状态标记为已初始化。如果 sync.Once 实例已经初始化,Do 方法将不会再次执行提供的函数。

sync.Once 的内部实现使用了互斥锁来保证线程安全,但它的 API 设计使得开发者不需要直接处理锁的获取和释放,从而简化了代码并减少了出错的可能性。

type Once struct {
	// done indicates whether the action has been performed.
	// It is first in the struct because it is used in the hot path.
	// The hot path is inlined at every call site.
	// Placing done first allows more compact instructions on some architectures (amd64/386),
	// and fewer instructions (to calculate offset) on other architectures.
	done atomic.Uint32
	m    Mutex
}
func (o *Once) Do(f func()) {
	// Note: Here is an incorrect implementation of Do:
	//
	//	if o.done.CompareAndSwap(0, 1) {
	//		f()
	//	}
	//
	// Do guarantees that when it returns, f has finished.
	// This implementation would not implement that guarantee:
	// given two simultaneous calls, the winner of the cas would
	// call f, and the second would return immediately, without
	// waiting for the first's call to f to complete.
	// This is why the slow path falls back to a mutex, and why
	// the o.done.Store must be delayed until after f returns.

	if o.done.Load() == 0 {
		// Outlined slow-path to allow inlining of the fast-path.
		o.doSlow(f)
	}
}

func (o *Once) doSlow(f func()) {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done.Load() == 0 {
		defer o.done.Store(1)
		f()
	}
}

代码实例

// 定义包级别的变量,用于存储单例实例和确保单例只被创建一次的sync.Once实例
var (
	sl   *singleInstance // 存储singleInstance单例实例
	once sync.Once       // 确保单例只被创建一次
)

// singleInstance定义了一个结构体,用于实现单例模式
type singleInstance struct{}

// Work方法实现了SingleLazyOnce接口的Work方法,用于实际的工作逻辑
func (s *singleInstance) Work() {
	fmt.Println("Doing some work...")
}

// newsingleLazyOnce函数内部返回一个新的singleInstance实例
func newsingleLazyOnce() *singleInstance {
	return &singleInstance{}
}

// SingleLazyOnce接口定义了Work方法,用于懒加载单例模式
type SingleLazyOnce interface {
	Work()
}

// GetSingleInstance函数返回singleInstance的单例实例。
// 该函数使用sync.Once确保整个应用程序中只创建一次singleInstance实例。
func GetSingleInstance() SingleLazyOnce {
	once.Do(func() {
		sl = newsingleLazyOnce() // 确保单例只被创建一次
	})
	return sl // 返回单例实例
}

总结

饿汉式单例模式:

  • 优点:在程序启动时立即创建实例,因此不存在线程安全问题,无需加锁,避免了同步控制带来的性能开销。
  • 缺点:由于实例在程序加载时即创建,即使该实例未被使用,也会导致资源的提前占用和浪费,不适用于按需创建的场景。
  • 适用场景:适合对性能要求高且实例使用频率高的场景。

懒汉式单例模式:

  • 优点:延迟实例化,仅在首次请求时创建对象,节省了内存资源,适用于实例使用频率不高的情况。
  • 缺点:需要通过加锁来确保多线程环境下的同步访问,这会带来一定的性能损耗。由于锁的粒度较大,即使在第一次创建对象后,后续访问仍需等待锁的释放。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值