golang实现设计模式之单例模式总结-代码、优缺点、适用场景
在日常业务开发中,适当应用设计模式,可以实现我们的业务需求,例如全局唯一配置,这里就需要用到单例模式。
什么情况下,我们可以使用单例模式呢?
这得考虑该模式的适用场景:
- 用来控制类型实例的数量的,当需要确保一个类型只有一个实例
单例模式的适用场景:
- 1.统计当前在线人数(网站计数器):用一个全局对象来记录。
- 2.数据库连接池(控制资源):一般是采用单例模式,因为数据库连接是一种连接数据库资源,不易频繁创建和销毁,如缓存或者数据库等连接池对象。
- 3.应用程序的日志(资源共享):一般日志内容是共享操作,需要在后面不断写入内容所以通常单例设计。
- 4.应用配置:全局唯一实例。
- 5.定义唯一属性:如特定的字符串。
- 6.多线程的线程池设计。
单例模式优缺点
优点
- 1.减少内存开销,尤其是频繁的创建和销毁实例。
- 2.避免对资源对过多占用。
缺点
- 1.没有抽象层,不能继承扩展很难。
- 2.违背了“单一职责原则”,一个类只重视内部关系,而忽略外部关系。
- 3.不适用于变化对象。
- 4.滥用单例会出现一些负面问题:如连接池溢出,长时间不被使用,被GC。
单例模式的实现模式
在单例模式中,通常有懒汉模式和饿汉模式,区别在于是否程序启动时就创建实例。
饿汉模式
程序初始化时即创建,可能即使创建不一定使用,造成浪费,但可以及早暴露问题,定位问题。
该模式下的实现细节:
- 1.单例类和构造方法不可导出,避免外部直接获取。
- 2.全局变量中声明。
- 3.初始化时,生成相关实例,并提供唯一外部接口获取实例。
代码实现:
package hungry // 不可导出的变量、结构体、构造函数 type singletonHungry struct{} var s *singletonHungry func init() { s = &singletonHungry{} } // 唯一外部接口 func GetInstance() *singletonHungry { return s }
懒汉模式
真正使用时再创建,需要注意并发安全问题,比如实例还未创建,有2个线程同时访问,这里就需要控制只会创建一个实例,通常来说可以通常加互斥锁实现,但也带来一定的性能问题,有时也需要通过 double check 解决。
该模式的实现细节:
- 1.单例类和构造方法不可导出,避免外部直接获取。
- 2.全局变量中声明。
- 3.第一次创建时,通过GetInstance() xxx 创建实例。
比较优雅的 golang 实现中,我们可以通过 sync.Once
提供的接口实现,或者借助于 sync.Mutex + atomic.Load/atomic.Store
实现,二者实现是类似的,都能很好解决并发安全且只有一个实例的问题。
代码实现:
package lazy import ( "sync" "sync/atomic" ) type singletonLazy struct{} var s *singletonLazy var once sync.Once func GetInstance() *singletonLazy { once.Do(func() { s = &singletonLazy{} }) return s } var done uint32 var mu sync.Mutex func GetInstanceV2() *singletonLazy { // 原子操作,减少锁开销,类似于sync.Once的处理流程 if atomic.LoadUint32(&done) == 1 { return s } mu.Lock() defer mu.Unlock() if atomic.LoadUint32(&done) == 0 { atomic.StoreUint32(&done, 1) s = &singletonLazy{} } return s }
参考文档:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南