面试-手写设计模式
单例模式
④ 双重检查锁定(互斥锁实现)
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"} 不是原子操作,创建一个对象实例,可以分为三步:
- 分配实例内存;
- 调用构造器方法,执行初始化;
- 将实例引用赋值给变量。
在实际运行时,2 和 3 的顺序有可能发生变化,就导致了second goroutine 读的实例可能只是分配了内存,但实例还没有初始化完成。
-----------------------------------
以上方法要么存在数据竞争问题:①③④,要么性能太低:②,具体解决思路见下面几种方法。
⑤ 双重检查锁定(读写锁实现)
为了避免这种 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 释放写锁,也就不会发生数据竞争。改动后的逻辑如下图所示:
⑥ 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 去运行代码,没有数据竞争的出现,通过下图分析代码逻辑。
⑦ 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()
}
}

浙公网安备 33010602011771号