创建型 单例设计模式

为什么需要单例

一个类只允许创建一个对象。

  1. 解决访问资源冲突,有些资源只能单一访问。在我们不想加锁的情况,可以通过只允许创建单一对象来访问。
  2. 节省系统资源,例如文件系统句柄(handle),通讯连接(socket)
  3. 表示全局唯一资源,一般用在配置信息类那里。

如何实现单例模型

一般来说,要关注几个要求:

  1. 构造函数必须是private,避免外部可以创建“新” new 实例
  2. 对象创建的时候是否有线程安全问题
  3. 是否需要支持延迟加载(我个人觉得延迟加载一般都是坑爹的,好处是程序初始化会快点,但是你用的时候要还回来的,而且经常会搞出线程创建坑)
  4. 提高取得实例的速度

饿汉式 vs 懒汉式

饿汉时就是初始化的时候就把实例搞出来 懒汉式就是延迟加载到具体用的时候再来创建实例(真的不推荐)

对于既要支持延迟加载又要注意高并发,Java 选手一般来说会用double check这种方式,就是看看实例创建没有,没有就加锁然后去创建。


public static SingleObject getSingle(){
    if (instance == null){
        synchronized(SingleObject.class){
                if (instance == null){ // 注意这里要再查一次
                    instance = new SingleObject();
                }
        }
    }
    return instance;
}

Golang 实现

我实现了包括饿汉和懒汉两种方式。

其中饿汉模式,我实现了一个允许有三个实例的单例模型。

go.mod

module singleton

go 1.13

singleton.go

package singleton

import (
	"fmt"
	"sync"
)

// singleton must be lowercase to restrict access
type singleton struct{}

var instances map[int]*singleton
var limitedNumber int = 3

func init() {
	instances = make(map[int]*singleton)
	for i := 0; i < limitedNumber; i++ {
		instances[i] = &singleton{}
	}
}

func GetInstance() *singleton {
	return GetInstanceById(0)
}

func GetInstanceById(id int) *singleton {
	if id >= limitedNumber {
		return nil
	}
	return instances[id]
}

func (s *singleton) DoSomething() {
	fmt.Println("do something...")
}

var lock = &sync.Mutex{}

type lazysingleton struct{}

func (s *lazysingleton) DoSomething() {
	fmt.Println("do something lazy...")
}

var lazySingletonInstance *lazysingleton
var LazyCount int

func GetLazyInstance() *lazysingleton {
	if lazySingletonInstance == nil {
		lock.Lock()
		defer lock.Unlock()
		// double check here
		if lazySingletonInstance == nil {
			LazyCount++
			// fmt.Println("This shall only be printed once")
			lazySingletonInstance = &lazysingleton{}
		} else {
			// fmt.Println("Lazy singleton is already created - 1")
		}
	} else {
		// fmt.Println("Lazy singleton is already created - 2")
	}
	return lazySingletonInstance
}

singleton_test.go 我这里使用了black box测试模式,更加贴近实际使用

package singleton_test

import (
	"testing"

	"singleton"
)

func TestSingleton(t *testing.T) {
	ins1 := singleton.GetInstance()
	ins2 := singleton.GetInstance()

	if ins1 != ins2 {
		t.Fatal("Instance is not equal")
	}
}

func TestMutipleLazyInit(t *testing.T) {
	for i := 0; i < 100; i++ {
		go singleton.GetLazyInstance()
	}
	if singleton.LazyCount != 1 {
		t.Fatal("Somewhere is messed up with lazy singleton init")
	}
}

func TestLazySingleton(t *testing.T) {
	ins1 := singleton.GetLazyInstance()
	ins2 := singleton.GetLazyInstance()
	if ins1 != ins2 {
		t.Fatal("Lazy Instance is not equal")
	}
}

执行: go test singleton -v

Previous
Next