单例模式介绍
单例模式是 创建型设计模式 的一种,核心目标是:确保一个类在整个应用中仅有一个实例,并提供一个全局唯一的访问点。
它常用于管理全局资源(如配置文件、数据库连接池、日志器、线程池等),避免重复创建实例导致的资源浪费(如频繁连接数据库)或状态不一致(如配置信息冲突)。
单例模式必须满足 3 个关键条件:
- 构造方法私有化:禁止外部通过
new关键字创建实例(杜绝随意创建多个对象); - 自身持有唯一实例:在类内部定义静态成员变量,存储该类的唯一实例;
- 提供全局访问点:通过静态方法(如
getInstance())向外部暴露唯一实例。
Go 没有类和构造方法的概念,单例模式通过 包级私有变量 + 公开函数 实现,核心思路:保证实例唯一 + 全局访问,同时需兼顾 Go 特有的 并发安全、懒加载 等特性。
Go 实现单例的关键约束:
- 实例变量用小写字母开头(
instance),保证包级私有(外部无法直接访问); - 提供大写字母开头的公开函数(如
GetInstance())作为全局访问点; - 并发安全:Go 天生支持并发,需通过
sync包工具避免多协程重复创建实例。
单例模式常见实现
饿汉式(立即初始化)
核心思想:程序启动时(包初始化阶段)直接创建实例,天然并发安全(Go 包初始化是单线程执行的)。
package singleton
var instance *HungrySingleton
type HungrySingleton struct {
Config string
}
func init() {
instance = &HungrySingleton{
Config: "全局配置信息",
}
}
func GetHungryInstance() *HungrySingleton {
return instance
}
优缺点:
- ✅ 优点:实现极简、无并发安全问题、访问效率高(无锁);
- ❌ 缺点:懒加载失效(实例提前创建),若实例占用资源大且从未被使用,会造成浪费。
适用场景:
实例占用资源少、肯定会被使用的场景(如全局配置类)。
懒汉式(延迟初始化,并发安全版)
核心思想:首次调用 GetInstance() 时才创建实例(懒加载),通过 sync.Mutex 互斥锁保证并发安全。
package singleton
import "sync"
var (
lazyInstance *LazySingleton
mutex sync.Mutex
)
type LazySingleton struct {
Data string
}
func GetLazyInstance() *LazySingleton {
mutex.Lock()
defer mutex.Unlock()
if lazyInstance == nil {
lazyInstance = &LazySingleton{
Data: "延迟初始化的数据",
}
}
return lazyInstance
}
优缺点:
- ✅ 优点:懒加载(按需创建,节省资源)、并发安全;
- ❌ 缺点:每次调用
GetInstance()都要加锁解锁,即使实例已创建,仍有锁开销,高并发场景效率低。
适用场景:
低并发、实例占用资源大且不常用的场景。
双重检查锁定(DCL,Double-Check Locking)
核心思想:优化懒汉式的锁开销,仅在实例未创建时加锁,通过 sync.Once 或 atomic 包避免 “虚假唤醒” 问题(Go 中不推荐直接用 if+mutex 双重检查,推荐用 sync.Once 简化)。
常规实现(DCL)
package main
import (
"fmt"
"sync"
)
var lock = &sync.Mutex{}
type single struct {
}
var singleInstance *single
func getInstance() *single {
if singleInstance == nil {
lock.Lock()
defer lock.Unlock()
if singleInstance == nil {
fmt.Println("Creating single instance now.")
singleInstance = &single{}
} else {
fmt.Println("Single instance already created.")
}
} else {
fmt.Println("Single instance already created.")
}
return singleInstance
}
标准实现(用 sync.Once 简化 DCL):
sync.Once 是 Go 提供的并发原语,保证某个函数仅执行一次,比手动双重检查更简洁、更安全(避免原子操作的复杂性)。
package singleton
import "sync"
var (
dclInstance *DCLSingleton
once sync.Once
)
type DCLSingleton struct {
Conn string
}
func GetDCLInstance() *DCLSingleton {
if dclInstance == nil {
once.Do(func() {
dclInstance = &DCLSingleton{
Conn: "mysql://localhost:3306/test",
}
})
}
return dclInstance
}
关键说明:
sync.Once内部通过 “互斥锁 + 原子操作” 实现,既保证并发安全,又避免重复初始化;- 比 Java 的 DCL 更简洁,无需考虑指令重排(Go 内存模型保证
once.Do()中的初始化操作对所有协程可见)。
优缺点:
- ✅ 优点:懒加载、并发安全、高并发效率高(仅首次初始化加锁)
适用场景:
高并发场景下的懒加载单例(如数据库连接池、缓存实例),是 Go 中最常用的工业级实现。
枚举单例(基于 iota,简化版)
Go 没有枚举类型,但可通过 iota 模拟枚举,结合单例思想实现 “唯一实例 + 常量级访问”,适用于全局常量、核心工具类。
package singleton
type EnumSingleton int
const (
Instance EnumSingleton = iota
)
func (e EnumSingleton) DoSomething() string {
return "枚举单例执行操作:全局唯一实例"
}
func GetEnumInstance() EnumSingleton {
return Instance
}
用法:
func main() {
singleton.Instance.DoSomething()
inst := singleton.GetEnumInstance()
inst.DoSomething()
}
优缺点:
- ✅ 优点:实现极简、天然唯一、并发安全、无资源浪费;
- ❌ 缺点:懒加载失效(程序启动时即定义),仅适用于 “无状态” 单例(如工具类、常量)。
适用场景:
全局常量、无状态工具类(如日志器、格式转换器),是 Go 中最简洁的单例实现。
带参数的单例(支持初始化配置)
核心思想:单例实例需要依赖外部参数(如数据库地址、端口)时,需在首次调用时传入参数初始化,后续调用直接返回实例。
package singleton
import "sync"
var (
paramInstance *ParamSingleton
paramOnce sync.Once
initParams struct {
Addr string
Port int
}
)
type ParamSingleton struct {
Addr string
Port int
}
func GetParamInstance(addr string, port int) *ParamSingleton {
once.Do(func() {
paramInstance = &ParamSingleton{
Addr: addr,
Port: port,
}
initParams.Addr = addr
initParams.Port = port
})
if addr != initParams.Addr || port != initParams.Port {
panic("单例已初始化,参数不一致")
}
return paramInstance
}
用法:
func main() {
inst1 := singleton.GetParamInstance("localhost", 8080)
inst2 := singleton.GetParamInstance("localhost", 8080)
fmt.Println(inst1 == inst2)
}
适用场景:
实例需要依赖外部配置(如数据库连接池、Redis 客户端)的场景。
关键注意点
并发安全的核心工具
sync.Mutex:互斥锁,适用于简单场景,但每次调用都加锁,效率较低;sync.Once:推荐用于懒加载单例,保证初始化函数仅执行一次,兼顾安全和效率;- 避免使用
atomic包手动实现双重检查(复杂且易出错,sync.Once已封装最优逻辑)。
禁止复制单例实例
Go 中结构体默认支持值复制,若单例被复制,会导致多个实例,破坏单例特性。解决方案:
- 让单例结构体实现
Unsupported接口,禁止复制(编译期检查); - 仅对外暴露指针(
*Singleton),避免值传递。
示例(禁止复制):
type Singleton struct{}
// 编译期检查:若尝试复制 Singleton,会报编译错误
var _ = func(s Singleton) {}(&Singleton{})
单例的生命周期
Go 中单例实例的生命周期与程序一致(直到程序退出),若需手动销毁单例(如释放资源),可添加 Destroy() 函数:
func DestroyInstance() {
mutex.Lock()
defer mutex.Unlock()
if instance != nil {
instance.Conn.Close()
instance = nil
}
}
总结
| 实现方式 | 懒加载 | 并发安全 | 支持参数 | 适用场景 |
|---|---|---|---|---|
| 饿汉式 | ❌ | ✅ | ❌ | 实例占用资源少、必被使用 |
| 普通懒汉式(Mutex) | ✅ | ✅ | ✅ | 低并发、需懒加载 |
| 双重检查(sync.Once) | ✅ | ✅ | ✅ | 高并发、需懒加载(推荐) |
| 枚举单例 | ❌ | ✅ | ❌ | 无状态工具类、全局常量 |
推荐优先级:
- 无参数 + 懒加载 + 高并发:
sync.Once双重检查模式; - 无参数 + 必被使用:饿汉式;
- 无状态工具类:枚举单例;
- 需传入初始化参数:带参数的
sync.Once模式。