Go 和 Java 对比学习:单例模式( 二 )

主要区别在于 getInstance 的实现,要注意 synchronized ,避免多线程时出现问题 。
3、单例模式的 Go 实现在 Go 语言中如何实现单例模式,类比 Java 代码实现 。
// 饿汉式单例模式package singletontype singleton struct {  count int}var Instance = new(singleton)func (s *singleton) Add() int {  s.count++  return s.count}前面说了,Go 只支持部分面向对象的特性,因此看起来有点不太一样:

  • 类(结构体 singleton)本身非公开(小写字母开头,非导出);
  • 没有提供导出的 GetInstance 工厂方法(Go 没有静态方法),而是直接提供包级导出变量 Instance;
这样使用:
c := singleton.Instance.Add()看看懒汉式单例模式在 Go 中如何实现:
// 懒汉式单例模式package singletonimport ( "sync")type singleton struct {  count int}var (  instance *singleton  mutex sync.Mutex)func New() *singleton {  mutex.Lock()  if instance == nil {    instance = new(singleton)  }  mutex.Unlock()    return instance}func (s *singleton) Add() int {  s.count++  return s.count}代码多了不少:
  • 包级变量变成非导出(instance),注意这里类型应该用指针,因为结构体的默认值不是 nil;
  • 提供了工厂方法,按照 Go 的惯例,我们命名为 New();
  • 多 goroutine 保护,对应 Java 的 synchronized,Go 使用 sync.Mutex;
关于懒汉式有一个“双重检查”,这是 C 语言的一种代码模式 。
在上面 New() 函数中,同步化(锁保护)实际上只在 instance 变量第一次被赋值之前才有用 。在 instance 变量有了值之后,同步化实际上变成了一个不必要的瓶颈 。如果能够有一个方法去掉这个小小的额外开销,不是更加完美吗?因此出现了“双重检查” 。看看 Go 如何实现“双重检查”,只看 New() 代码:
func New() *singleton {  if instance == nil { // 第一次检查(①)    // 这里可能有多于一个 goroutine 同时达到(②)    mutex.Lock()    // 这里每个时刻只会有一个 goroutine(③)    if instance == nil { // 第二次检查(④)      instance = new(singleton)    }    mutex.Unlock()  }    return instance}有读者可能看不懂上面代码的意思,这里详细解释下 。假设 goroutine X 和 Y 作为第一批调用者同时或几乎同时调用 New 函数 。
  1. 因为 goroutine X 和 Y 是第一批调用者,因此,当它们进入此函数时,instance 变量是 nil 。因此 goroutine X 和 Y 会同时或几乎同时到达位置 ①;
  2. 假设 goroutine X 会先达到位置 ②,并进入 mutex.Lock() 达到位置 ③ 。这时,由于 mutex.Lock 的同步限制,goroutine Y 无法到达位置 ③,而只能在位置 ② 等候;
  3. goroutine X 执行 instance = new(singleton) 语句,使得 instance 变量得到一个值,即对 singleton 实例的引用 。此时,goroutine Y 只能继续在位置 ② 等候;
  4. goroutine X 释放锁,返回 instance,退出 New 函数;
  5. goroutine Y 进入 mutex.Lock(),到达位置 ③,进而到达位置 ④ 。由于 instance 变量已经不是 nil,因此 goroutine Y 释放锁,返回 instance 所引用的 singleton 实例(也就是 goroutine X 锁创建的 singleton 实例),退出 New 函数;
到这里,goroutine X 和 Y 得到了同一个 singleton 实例 。可见上面的 New 函数中,锁仅用来避免多个 goroutine 同时实例化 singleton 。
相比前面的版本,双重检查版本,只要 instance 实例化后,锁永远不会执行了,而前面版本每次调用 New 获取实例都需要执行锁 。性能很显然,我们可以基准测试来验证:(双重检查版本 New 重命名为 New2)
package singleton_testimport ( "testing" "github.com/polaris1119/go-demo/singleton")func BenchmarkNew(b *testing.B) { for i := 0; i < b.N; i++ {  singleton.New() }}func BenchmarkNew2(b *testing.B) { for i := 0; i < b.N; i++ {  singleton.New2() }}


推荐阅读