golang-设计模式-单例模式-学习笔记

【1】基本理论    
        定义:
        单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
        常见的使用场景:
        全局锁,配置信息,唯一递增ID号码生成器等。
        单例的几种经典实现方式特点:
        1. 饿汉式
        优点:
        饿汉式的实现方式比较简单。在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。
        采用饿汉式实现方式,将耗时的初始化操作,提前到程序启动的时候完成,这样就能避免在程序运行的时候,再去初始化导致的性能问题。
        缺点:
        存在的问题是不支持延迟加载。
        2.懒汉式
        优点:
        支持延迟加载
        缺点:
        为了保障线程安全,需要加锁保护,会导致频繁加锁、释放锁,以及并发度低等问题,频繁的调用会产生性能瓶颈。
        3.双重检测
        饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。双重检测模式既支持延迟加载、又支持高并发的单例。
        双重检测可能存在指令重排的问题,导致实例还没有初始化,就被另一个线程使用了。低版本Java可以给instance成员变量加上volatile关键字,禁止指令重排序。
        高版本的Java已经在JDK内部实现中解决了这个问题(解决的方法很简单,只要把对象new操作和初始化操作设计为原子操作,就自然能禁止重排序)。
        4.静态类型
        比如:Java语言的静态内部类来实现单例。这种实现方式,既支持延迟加载,也支持高并发,实现起来也比双重检测简单。
        C++11中,静态局部变量是在第一次调用方法时分配内存的,C++11保证了静态局部对象是线程安全的,直接返回静态局部变量就可以轻松实现安全高效的单例类。

【2】功能描述
        1.单例创建Map
        2.单例创建struct&并在多协程中验证

【3】设计思路
        sync.Once 功能:
        sync.Once 是 Golang package 中使方法只执行一次的对象实现,作用与 init 函数类似。但也有所不同。init 函数是在文件包首次被加载的时候执行,且只执行一次。sync.Once 是在代码运行中需要的时候执行,且只执行一次。当一个函数不希望程序在一开始的时候就被执行的时候,我们可以使用 sync.Once。
        如果执行的内容是创建一个对象实例,那么刚好可以满足创建单例类的需求。
        ​​​​​​​sync.Once 原理:
        sync.Once 使用变量 done 来记录函数的执行状态,使用 sync.Mutex 和 sync.atomic 来保证线程安全的读取done。Once.Do(f func())本质上是一个装饰器,通过函数闭包,包裹对无参数无返回值的函数f的调用。

        下面是sync.Once对应源码( /go/src/sync/once.go):

package sync

import (
	"sync/atomic"
)

// Once is an object that will perform exactly one action.
type Once struct {
	m    Mutex
	done uint32 // 初始值为0表示还未执行过,1表示已经执行过
}


func (o *Once) Do(f func()) {
		// 判断done是否为0,若为0,表示未执行过,调用doSlow()方法初始化
        if atomic.LoadUint32(&o.done) == 0 {
                // Outlined slow-path to allow inlining of the fast-path.
                o.doSlow(f)
        }
}

func (o *Once) doSlow(f func()) {
        o.m.Lock()
        defer o.m.Unlock()
		// 采用双重检测机制 加锁判断done是否为零
        if o.done == 0 {
		// 执行完f()函数后,将done值设置为1
                defer atomic.StoreUint32(&o.done, 1)
		// 执行传入的f()函数
                f()
        }
}

        在 doSlow() 函数中,若并发的goroutine进入该函数中,为了保证仅有一个goroutine执行 f() 匿名函数。为此,需要加互斥锁保证只有一个goroutine进行初始化,同时采用了双检查的机制(double-checking),再次判断 o.done 是否为 0,如果为 0,则是第一次执行,执行完毕后,就将 o.done 设置为 1,然后释放锁。

        即使此时有多个 goroutine 同时进入了 doSlow 方法,因为双检查的机制,后续的 goroutine 会看到 o.done 的值为 1,也不会再次执行 f。

        //优点
设计存储实例生成后的状态标识字段done,利用atomic的原子操作,如果设置了,直接返回实例;如果没设置,表示可以创建实例,再去抢锁,可以把锁操作后置,有效的防止了每次锁判断的处理。

【4】Demo实战   

        1.单例创建Map
        2.单例创建struct&并在多协程中验证

        Demo代码:

package main

import (
	"fmt"
	"sync"
	"unsafe"
)

//
//1.单例创建Map

type singleton map[string]string

var (
	once sync.Once

	instance singleton
)

func New() singleton {
	once.Do(func() {
		instance = make(singleton)
	})

	return instance
}

//
//2.单例创建struct
type RedisConfig struct {
	ip   string
	port int
	auth string
}

var (
	once2         sync.Once
	redisInstance *RedisConfig
)

func GetRedisInstance() *RedisConfig {
	once2.Do(func() {
		//mock read from file
		rdIp := "127.0.0.1"
		rdPort := 6379
		rdAuth := "dadaaaxx"
		fmt.Println("Create RedisConfig")
		redisInstance = &RedisConfig{
			ip:   rdIp,
			port: rdPort,
			auth: rdAuth,
		}
	})
	return redisInstance
}

//
//测试函数
func TestCreateMapSingleton() {
	fmt.Println("++++++TestCreateMapSingleton beg")
	s := New()
	s["this"] = "that"
	s2 := New()
	fmt.Println("This is ", s2["this"])
	fmt.Println("++++++TestCreateMapSingleton end")

}

func TestCreateStructSingleton() {
	fmt.Println("++++++TestCreateStructSingleton beg")
	var wg sync.WaitGroup
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go func() {
			obj := GetRedisInstance()
			fmt.Printf("%X\n", unsafe.Pointer(obj))
			fmt.Println(*redisInstance)
			wg.Done()
		}()
	}
	wg.Wait()
	fmt.Println("++++++TestCreateStructSingleton end")

}

//

func main() {
	TestCreateMapSingleton()
	TestCreateStructSingleton()
}

        运行结果:

++++++TestCreateMapSingleton beg
This is  that
++++++TestCreateMapSingleton end
++++++TestCreateStructSingleton beg
Create RedisConfig
C00006C180
{127.0.0.1 6379 dadaaaxx}
C00006C180
{127.0.0.1 6379 dadaaaxx}
C00006C180
{127.0.0.1 6379 dadaaaxx}

你可能感兴趣的:(Golang开发,golang,单例模式)