源自菜鸟教程:
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
在实现上有懒汉方式,饿汉方式,懒汉加锁,双重锁,sync.Once实现等不同的实现方法
区别:
1. 饿汉就是类一旦加载,就把单例初始化完成,保证GetInstance的时候,单例是已经存在的了
2. 而懒汉方式,只有当调用GetInstance的时候,会去初始化这个单例
3. 饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题
4. 懒汉式本身是非线程安全的,为了实现线程安全有几种写法
1. 懒汉方式
package main
import "fmt"
// 缺点:非线程安全;
// 当正在创建时,有线程来访问此时ins = nil就会再创建,单例类就会有多个实例了;
type Singleton struct{
// 结构体中的一些字段
}
var instance *Singleton // 全局的一个实例
func GetInstance() *Singleton{
if instance == nil {
fmt.Println("懒汉方式--创建单例模式中的实例.")
instance = &Singleton{} //此处初始化实例
}
return instance
}
2. 饿汉方式
package main
import "sync"
import "fmt"
// 缺点是在导入包的同时会创建该对象,并持续占有在内存中
type Singleton struct{
// 结构体中的一些字段
}
var instance *Singleton = &Singleton{} // 此处已经创建好了
func GetInstance() *Singleton{
return instance
}
3. 懒汉加锁
package main
import "sync"
import "fmt"
// Sync.Mutex进行加锁,保证线程安全,但由于每次调用该方法都进行了加锁操作,在性能上相对不高效
type Singleton struct {
// 结构体中的一些字段
}
var instance *Singleton // 全局的一个实例
var m sync.Mutex // 锁
func GetInstance() *Singleton {
m.Lock()
defer m.Unlock()
if instance == nil {
fmt.Println("懒汉加锁方式--创建单例模式中的实例.")
instance = &Singleton{}
}
return instance
}
4. 双重锁/双重检查
package main
import "sync"
import "fmt"
// 在懒汉式(线程安全)的基础上再进行忧化,判少加锁的操作。保证线程安全同时不影响性能
type Singleton struct {
// 结构体中的一些字段
}
var instance *Singleton // 全局的一个实例
var m sync.Mutex
func GetInstance() *Singleton {
if instance == nil {
m.Lock()
defer m.Unlock()
if instance == nil {
fmt.Println("双重检查方式--创建单例模式中的实例.")
ins = &Singleton{}
}
}
5. sync.Once(推荐)
package main
import "sync"
import "fmt"
type Singleton struct {
// 结构体中的一些字段
}
var instance *Singleton // 全局的一个实例
var once sync.Once // sync.Once只有一个Do方法,可以保证这个函数f只执行一次 func (o *Once) Do(f func())
func GetInstance() *Singleton {
once.Do(func() {
fmt.Println("sync.Once方式--创建单例模式中的实例.")
instance = &Singleton{} //此处初始化实例
})
return instance
}
补充:
1. sync.Once是golang提供的方法,一旦执行一次,就不再执行
2. sync.Once内部本质上也是双重检查的方式,但在写法上会比自己写双重检查更简洁,Once的源码如下:
func (o *Once) Do(f func()) {
//判断是否执行过该方法,如果执行过则不执行
if atomic.LoadUint32(&o.done) == 1 {
return
}
// Slow-path.
o.m.Lock()
defer o.m.Unlock()
//进行加锁,再做一次判断,如果没有执行,则进行标志已经扫行并调用该方法
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}