一个类只允许创建一个对象(或实例),那这个类就是一个单例类,这种设计模式就叫做单例设计模式(Singleton Design Pattern)
单例模式分为 饿汉式
和 懒汉式
两种实现
- 饿汉式:初始化的时候已经创建好实例
- 懒汉式:只有在调用的时候才会初始化
构建的时候注意:
- 构造函数是私有访问权限(防止被其他地方重新构建)
- 构建的时候考虑并发情况
- 考虑是否支持延迟加载
源码路径:https://github.com/XBoom/DesignPatterns/tree/main/01_Singleton
饿汉式
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
type HungrySingleton struct { }
var hungrySingleton *HungrySingleton
func init() { hungrySingleton = new(HungrySingleton) }
func GetHungryInstance() *HungrySingleton { return hungrySingleton }
|
单元测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func TestGetHungryInstance(t *testing.T) { ins1 := GetHungryInstance() ins2 := GetHungryInstance() if ins1 != ins2 { t.Fatal("instance is not equal") } }
func BenchmarkGetHungryInstance(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { if GetHungryInstance() != GetHungryInstance() { b.Errorf("test fail") } } }) }
|
性能测试结果
1 2 3 4 5
| pkg: DesignPatterns/01_Singleton cpu: Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz BenchmarkGetHungryInstance BenchmarkGetHungryInstance-8 1000000000 0.2271 ns/op PASS
|
懒汉式
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13
| type LazybonesSingleton struct { }
var once sync.Once var lazybonesSingleton *LazybonesSingleton
func GetLazybonesInstance() *LazybonesSingleton { once.Do(func() { lazybonesSingleton = new(LazybonesSingleton) }) return lazybonesSingleton }
|
单元测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| func TestGetLazybonesInstance(t *testing.T) { ins1 := GetLazybonesInstance() ins2 := GetLazybonesInstance() if ins1 != ins2 { t.Fatal("instance is not equal") } }
func BenchmarkGetLazybonesInstance(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { if GetLazybonesInstance() != GetLazybonesInstance() { b.Errorf("test fail") } } }) }
|
性能测试结果
1 2 3 4 5
| pkg: DesignPatterns/01_Singleton cpu: Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz BenchmarkGetLazybonesInstance BenchmarkGetLazybonesInstance-8 1000000000 0.7035 ns/op PASS
|
加了 Sync.Once
这里性能会低一点
完了吗?没有。可以明显看到 饿汉模式不支持延迟初始化 但 懒汉模式性能也不高,不如将两者结合起来,实现 双重检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| type HungryLazybones struct { }
var hungryLazybones *HungryLazybones
var onc sync.Once
func GetHungryLazybonesInstance() *HungryLazybones { if hungryLazybones == nil { onc.Do(func() { hungryLazybones = new(HungryLazybones) }) } return hungryLazybones }
|
另外在 Java 中还有两种方式来实现单例模式
方法 1:通过静态方法实现单例模式
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class IdGenerator { private AtomicLong id = new AtomicLong(0); private IdGenerator() {} private static class SingletonHolder{ private static final IdGenerator instance = new IdGenerator(); } public static IdGenerator getInstance() { return SingletonHolder.instance; } public long getId() { return id.incrementAndGet(); } }
|
SingletonHolder
是一个静态内部类,当外部类 IdGenerator
被加载的时候,并不会创建 SingletonHolder
实例对象。只有当调用 getInstance()
方法时,SingletonHolder
才会被加载,这个时候才会创建 instance
。insance
的唯一性、创建过程的线程安全性,都由 JVM 来保证。这种实现方法既保证了线程安全,又能做到延迟加载
方法 2:枚举类型
1 2 3 4 5 6 7
| public enum IdGenerator { INSTANCE; private AtomicLong id = new AtomicLong(0); public long getId() { return id.incrementAndGet(); } }
|
基于枚举类型的单例实现。通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。
问题
如何实现集群的单例模式
- 接获取原子锁(防止多个服务同时使用单例对象)
- 判断单例在共享区域是否存在,不存在则构建单例对象
- 将单例对象序列化并存储到外部共享存储区
- 使用单例对象
- 将对象显示地将对象从内存中删除
- 释放原子锁
总结
- 可拓展性差,如果需要多实例对象可能比较麻烦,适用于单实例对象
- 可测试性差,因为是唯一实例,进行多场景修改实例进行测试可能会比较麻烦
参考文档
- https://lailin.xyz/post/singleton.html
- https://github.com/senghoo/golang-design-pattern/blob/master/03_singleton/singleton.go