面试-手写设计模式

单例模式

④ 双重检查锁定(互斥锁实现)

type singleton struct {
	Name string
}

var (
	instance *singleton
    // 互斥锁实现
	lock     sync.Mutex
)

func GetInstance() *singleton {
	// 第一次判断是否为空
	if instance == nil {
		lock.Lock()
		defer lock.Unlock()
		// 为空时,第一个抢到锁的线程,第二次判断是否为空,为空开始初始化instance,
		// 后面抢到锁的线程会发现instance已经存在,因此直接返回
		if instance == nil {
			instance = &singleton{Name: "zhangsan"}
		}
	}
	return instance
}

func main() {
	for i := 0; i < 5; i++ {
		go func() {
			resp := GetInstance()
			log.Printf("the value is : %s", resp.Name)
		}()
	}
	time.Sleep(time.Second)
}

执行以上程序,程序打印正常,看起来是没有问题。但是当执行命令 go run --race main.go 去检查是否有数据竞争时,会报WARNING: DATA RACE ,表示存在数据竞争,即 first goroutine 在初始化实例时,second goroutine 正在读实例。

数据竞争是指: 至少有两个 goroutine 同时去访问一个变量,而这两个 goroutine 中至少有一个会写这个变量。

究其原因,instance = &singleton{Name: "zhangsan"} 不是原子操作,创建一个对象实例,可以分为三步:

  1. 分配实例内存;
  2. 调用构造器方法,执行初始化;
  3. 将实例引用赋值给变量。

在实际运行时,2 和 3 的顺序有可能发生变化,就导致了second goroutine 读的实例可能只是分配了内存,但实例还没有初始化完成。

image-20220724143424235.png

-----------------------------------

以上方法要么存在数据竞争问题:①③④,要么性能太低:②,具体解决思路见下面几种方法。

⑤ 双重检查锁定(读写锁实现)

为了避免这种 case 的出现,需要把原来的互斥锁改成粒度更小的读写锁,修改后的部分代码如下:

type singleton struct {
	Name string
}

var (
	instance *singleton
    // 读写锁实现
	lock     sync.RWMutex
)

func GetInstance() *singleton {
	// 第一次判断是否为空时,使用一个读锁,确保其它协程在抢到写锁、初始化instance时,
    // 这里会被阻塞,避免读到没有初始化完成的instance
	lock.RLock()
	ins := instance
	lock.RUnlock()
	if ins == nil {
		lock.Lock()
		defer lock.Unlock()
		// 若ins为空,第一个抢到锁的线程,第二次判断是否为空,为空开始初始化instance,
		// 后面抢到锁的线程会发现instance已经存在,因此直接返回
		if instance == nil {
			instance = &singleton{Name: "zhangsan"}
		}
	}
	return instance
}

改善后,再次通过 go run --race main.go 运行以上代码,就没有数据竞争的问题了。因为读写锁中,读锁和写锁是互斥的。所以 second goroutine 在 first goroutine 没有释放写锁之前,会一直 waiting ,直到 first goroutine 释放写锁,也就不会发生数据竞争。改动后的逻辑如下图所示:

image-20220724144720984.png

⑥ atomic 实现

④出现数据竞争的原因是: instance 变量在同一时刻既有 goroutine 读,也有 goroutine 写,本质原因是变量的修改非原子操作,而 golang 中提供了一个原子操作 "sync/atomic",atomic 的逻辑是实现在硬件层面之上,其意味着即使有多个 goroutine 修改同一个 atomic 变量,该变量也会正确更新且不会发生数据竞争。

type Singleton struct {
   Name string
}

var (
   instance *Singleton
   lock     sync.Mutex
   flag     uint32     // 标志位,默认为0,用于检查是否已初始化
)

func GetInstance() *Singleton {
   // 第一次检查:使用原子操作读取标志位,避免不必要的锁竞争
   // 如果 flag == 1 说明实例已初始化,直接返回
   if atomic.LoadUint32(&flag) == 0 {
      lock.Lock()
      defer lock.Unlock()
      // 第二次检查:防止多个goroutine通过第一次检查后重复初始化
      if atomic.LoadUint32(&flag) == 0 {
         // 使用defer确保在函数返回前更新标志位,
         // 原子操作保证flag的修改对其他goroutine立即可见
         defer atomic.StoreUint32(&flag, 1)
         instance = &Singleton{Name: "zhangsan"}
      }
   }
   return instance
}

同样使用 go run --race main.go 去运行代码,没有数据竞争的出现,通过下图分析代码逻辑。

image-20220726080259998.png

⑦ sync.Once 实现

golang 也提供了一种双重检查锁的方法 sync.Once,sync.Once 只暴露了一个方法 Do,你可以多次调用 Do 方法,但是只有第一次调用 Do 方法时 f 参数才会执行,这里的 f 是一个无参数无返回值的函数,sync.Once 源码如下:

package sync
 
import (
   "sync/atomic"
)
 
type Once struct {
	_ noCopy
	done atomic.Uint32
    /* atomic.Uint32 实际结构如下:
    type Uint32 struct {
        _ noCopy
        v uint32
	} */
	m    Mutex
}
 
func (o *Once) Do(f func()) {
    // 实际调用的 func LoadUint32(addr *uint32) (val uint32)
	if o.done.Load() == 0 {
         // 将慢路径提取到独立方法 doSlow,确保快路径方法 Do 可内联优化
		o.doSlow(f)
	}
}
 
func (o *Once) doSlow(f func()) {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done.Load() == 0 {
         // 实际调用的 func StoreUint32(addr *uint32, val uint32)
		defer o.done.Store(1)
		f()
	}
}

可以看出,sync.Once 的原理其实和 ⑥ 是一样的,只不过使用了 atomic 自己封的结构 atomic.Uint32,同时将慢路径 提取成了独立方法doSlow ,确保快路径方法 Do 能够被内联优化(因为如果把doSlow方法展开,快路径会因为包含锁操作(Lock()/Unlock())和 defer 而无法内联),能够减少函数调用开销。

用 sync.once 实现的单例模式如下:

type Singleton struct {
	Name string
}

var (
	instance *Singleton
	once     sync.Once
)

func GetInstance() *Singleton {
	once.Do(func() {
		instance = &Singleton{Name: "zhangsan"}
	})
	return instance
}

工厂模式

1)简单工厂模式

简单工厂接收一个参数,根据这个参数决定生产哪种产品。

例如:有一家生产鼠标的厂家,它只有一个工厂,能够生产两种型号的鼠标。客户需要什么样的鼠标,一定要显示地告诉生产工厂。

缺点:当要生产新的鼠标时,需要修改工厂函数,违反了开放封闭原则。

// Mouse 定义鼠标接口
type Mouse interface {
	Show()
}

// MouseMicro 微软鼠标实现
type MouseMicro struct{}

func (m *MouseMicro) Show() {
	fmt.Println("微软的鼠标")
}

// MouseLenovo 联想鼠标实现
type MouseLenovo struct{}

func (m *MouseLenovo) Show() {
	fmt.Println("联想的鼠标")
}

// CreateMouse 直接作为包级函数,无需工厂结构体
func CreateMouse(t string) Mouse {
    // 根据传入的参数决定生产哪种鼠标
	switch t {
	case "micro":
		return &MouseMicro{}
	case "lenovo":
		return &MouseLenovo{}
	default:
		return nil
	}
}

func main() {
	if mouse := CreateMouse("micro"); mouse != nil {
		mouse.Show()
	}
	
	if mouse := CreateMouse("lenovo"); mouse != nil {
		mouse.Show()
	}
}

2)工厂模式

是指父工厂定义一个用于创建对象的接口,子工厂继承父工厂,由子工厂决定生产哪种产品。将创建过程延迟到子类进行。

例如:生产鼠标的工厂,决定开设一个子工厂A专门生产微软的鼠标,再开设另一个子工厂B专门生产联想的鼠标。这时客户要微软的鼠标,就找A工厂要;要联想的鼠标,就找B工厂要。

缺点:每增加一个新产品,就需要增加一个父类工厂。

// Mouse 定义鼠标接口
type Mouse interface {
	Show()
}

// MouseMicro 微软鼠标实现
type MouseMicro struct{}
func (m *MouseMicro) Show() {
	fmt.Println("微软的鼠标")
}

// MouseLenovo 联想鼠标实现
type MouseLenovo struct{}
func (m *MouseLenovo) Show() {
	fmt.Println("联想的鼠标")
}

// Factory 工厂接口
type Factory interface {
	CreateMouse() Mouse
}

// MicroFactory 微软鼠标工厂
type MicroFactory struct{}
func (f *MicroFactory) CreateMouse() Mouse {
	return &MouseMicro{}
}

// LenovoFactory 联想鼠标工厂
type LenovoFactory struct{}
func (f *LenovoFactory) CreateMouse() Mouse {
	return &MouseLenovo{}
}

func main() {
	factories := []Factory{
		&MicroFactory{},
		&LenovoFactory{},
	}

	for _, factory := range factories {
		mouse := factory.CreateMouse()
		mouse.Show()
	}
}

3)抽象工厂模式

工厂模式Factory接口里面只有一个方法声明,如果我们再加一个呢或者多个呢?对,别怀疑,这个就是抽象工厂模式。

抽象工厂也就是不仅生产鼠标,同时生产键盘等PC上的所有部件,有生产鼠标,生产键盘等接口。微软工厂、联想工厂继承它,可以分别生产微软鼠标 + 戴尔键盘,和联想鼠标 + 惠普键盘。

package main

import "fmt"

// Mouse 鼠标接口
type Mouse interface {
	Show()
}

// KeyBoard 键盘接口
type KeyBoard interface {
	Show()
}

// MicrosoftMouse 微软鼠标
type MicrosoftMouse struct{}
func (m *MicrosoftMouse) Show() {
	fmt.Println("微软的鼠标")
}

// LenovoMouse 联想鼠标
type LenovoMouse struct{}
func (m *LenovoMouse) Show() {
	fmt.Println("联想的鼠标")
}

// MicrosoftKeyBoard 微软键盘
type MicrosoftKeyBoard struct{}
func (k *MicrosoftKeyBoard) Show() {
	fmt.Println("微软的键盘")
}

// LenovoKeyBoard 联想键盘
type LenovoKeyBoard struct{}
func (k *LenovoKeyBoard) Show() {
	fmt.Println("联想的键盘")
}

// Factory 抽象工厂接口
type Factory interface {
	CreateMouse() Mouse
	CreateKeyBoard() KeyBoard
}

// MicrosoftFactory 微软工厂
type MicrosoftFactory struct{}
// 创建微软鼠标
func (f *MicrosoftFactory) CreateMouse() Mouse {
	return &MicrosoftMouse{}
}
// 创建微软键盘
func (f *MicrosoftFactory) CreateKeyBoard() KeyBoard {
	return &MicrosoftKeyBoard{}
}

// LenovoFactory 联想工厂
type LenovoFactory struct{}
// 创建联想鼠标
func (f *LenovoFactory) CreateMouse() Mouse {
	return &LenovoMouse{}
}
// 创建联想键盘
func (f *LenovoFactory) CreateKeyBoard() KeyBoard {
	return &LenovoKeyBoard{}
}

func main() {
	factories := []Factory{
		&MicrosoftFactory{},
		&LenovoFactory{},
	}

	for _, factory := range factories {
		mouse := factory.CreateMouse()
		keyboard := factory.CreateKeyBoard()
		
		mouse.Show()
		keyboard.Show()
	}
}
posted @ 2025-07-03 15:52  如三月兮  阅读(19)  评论(0)    收藏  举报