设计模式-02-单例模式

一个类只允许创建一个对象(或实例),那这个类就是一个单例类,这种设计模式就叫做单例设计模式(Singleton Design Pattern)

单例模式分为 饿汉式懒汉式 两种实现

  1. 饿汉式:初始化的时候已经创建好实例
  2. 懒汉式:只有在调用的时候才会初始化

构建的时候注意:

  1. 构造函数是私有访问权限(防止被其他地方重新构建)
  2. 构建的时候考虑并发情况
  3. 考虑是否支持延迟加载

源码路径: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)
}

// GetHungryInstance 获取饿汉实例
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

// GetLazybonesInstance 获取懒汉实例
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
// HungryLazybones 懒汉模式与饿汉模式结合
type HungryLazybones struct {
}

var hungryLazybones *HungryLazybones

var onc sync.Once

// GetHungryLazybonesInstance 获取单例实例
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 才会被加载,这个时候才会创建 instanceinsance 的唯一性、创建过程的线程安全性,都由 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 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

问题

如何实现集群的单例模式

  1. 接获取原子锁(防止多个服务同时使用单例对象)
  2. 判断单例在共享区域是否存在,不存在则构建单例对象
  3. 将单例对象序列化并存储到外部共享存储区
  4. 使用单例对象
  5. 将对象显示地将对象从内存中删除
  6. 释放原子锁

总结

  1. 可拓展性差,如果需要多实例对象可能比较麻烦,适用于单实例对象
  2. 可测试性差,因为是唯一实例,进行多场景修改实例进行测试可能会比较麻烦

参考文档

  1. https://lailin.xyz/post/singleton.html
  2. https://github.com/senghoo/golang-design-pattern/blob/master/03_singleton/singleton.go