单例模式
问题
为什么要有单例模式?
第一是不想浪费资源,多次初始化会造成浪费。都用我的就好了。
其次是统一,如果是需要统一的东西,那就只创立一个入口,让大家只能用我的。
使用场景
- 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
- 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
- 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
- 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
- 频繁访问数据库或文件的对象。
- 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
实现
懒汉式:在使用的时候再初始化。针对要强制统一的情况。
饿汉式:最开始就初始化。针对不想浪费资源的情况,是大量使用的类。
代码
//Singleton 懒汉式单例
type Singleton struct{}
var singleton *Singleton
var once sync.Once
//GetInstance 用于获取单例模式对象
func GetInstance() *Singleton {
once.Do(func() {
singleton = &Singleton{}
})
return singleton
}
// Singleton2 饿汉式单例
type Singleton2 struct{}
var singleton2 *Singleton2
func init() {
singleton2 = &Singleton2{}
}
// GetInstance 获取实例
func GetInstance2() *Singleton2 {
return singleton2
}
单元测试
package singleton
import (
"sync"
"testing"
)
const parCount = 100
func TestSingleton(t *testing.T) {
ins1 := GetInstance()
ins2 := GetInstance()
if ins1 != ins2 {
t.Fatal("instance is not equal")
}
}
func TestParallelSingleton(t *testing.T) {
wg := sync.WaitGroup{}
wg.Add(parCount)
instances := [parCount]*Singleton{}
for i := 0; i < parCount; i++ {
go func(index int) {
instances[index] = GetInstance()
wg.Done()
}(i)
}
wg.Wait()
for i := 1; i < parCount; i++ {
if instances[i] != instances[i-1] {
t.Fatal("instance is not equal")
}
}
}
func TestSingleton2(t *testing.T) {
ins1 := GetInstance2()
ins2 := GetInstance2()
if ins1 != ins2 {
t.Fatal("instance is not equal")
}
}
func TestParallelSingleton2(t *testing.T) {
wg := sync.WaitGroup{}
wg.Add(parCount)
instances := [parCount]*Singleton2{}
for i := 0; i < parCount; i++ {
go func(index int) {
instances[index] = GetInstance2()
wg.Done()
}(i)
}
wg.Wait()
for i := 1; i < parCount; i++ {
if instances[i] != instances[i-1] {
t.Fatal("instance is not equal")
}
}
}
单例模式的优点:
- 单例模式可以保证内存里只有一个实例,减少了内存的开销。
- 可以避免对资源的多重占用。
- 单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点:
- 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
- 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
其实凡是有利就有弊,只要针对自己的场景选择合适的模式就好了。