美文网首页
【Go - 单例示例】

【Go - 单例示例】

作者: wn777 | 来源:发表于2024-07-24 01:21 被阅读0次

需求

实现一个单例,需要考虑如下的内容,

  • 最基础的,每次返回相同实例,
  • 线程安全,
  • 生命周期,资源管理,程序退出后释放资源,

参照以上3点需求,以下是一个完整的示例,

代码示例

package main

import (
    "context"
    "fmt"
    "sync"

    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

var (
    singletonMongoClient *mongo.Client
    once                 sync.Once
)

func getSingletonMongoClient() *mongo.Client {

    once.Do(func() {
        // 创建连接到 MongoDB 的客户端
        uri := "mongodb://localhost:27017"
        client, err := mongo.Connect(context.TODO(), options.Client().
            ApplyURI(uri))

        if err != nil {
            panic(err)
        }
        singletonMongoClient = client
    })
    return singletonMongoClient
}

func main() {
    client1 := getSingletonMongoClient()
    if client1 == nil {
        panic("client1 is nil")
    }

    fmt.Println("client1 address %p", client1)

    defer func() {
        if err := client1.Disconnect(context.TODO()); err != nil {
            panic(err)
        }
    }()

    client2 := getSingletonMongoClient()
    fmt.Println("client2 address %p", client2)

    if client1 == client2 {
        fmt.Println("client1 and client2 are the same")
    }
}

📢注意: 在使用多线程或多协程的情况下,这里可以使用sync.Once 保证在多线程情况下,也只执行一次,这个非常方便。

sync.Once的优势

对比下Java中实现单例 ,熟悉Java的朋友 ,可能了解 Java 领域一个经典的案例,利用双重检查创建单例对象。

public class Singleton {
  static Singleton instance;
  static Singleton getInstance(){
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
        }
    }
    return instance;
  }
}

这个示例初看一点问题没有,但是实际隐藏一个坑,问题就出在了new操作上,我们以为的new操作是这样的:

1.分配一块内存空间
2.在这块内存空间上初始化Singleton实例对象
3.把这个对象的内存地址赋值给instance变量

但实际上由于指令重排,优化后的过程是这样的:

1.分配一块内存空间
2.把这快内存空间的内存地址赋值给instance变量
3.在这块内存空间上初始化Singleton实例对象

那么这样调换顺序后会发生什么呢?

我们假设线程 A 先执行 getInstance() 方法,当执行完指令 2 时恰好发生了线程切换,切换到了线程 B 上;如果此时线程 B 也执行 getInstance() 方法,那么线程 B 在执行第一个判断时会发现 instance != null ,所以直接返回 instance,而此时的 instance 是没有初始化过的,如果我们这个时候访问 instance 的成员变量就可能触发空指针异常。

解决方案

针对指令重排序问题:在Java中,由于编译器优化,指令重排序的可能性,可能会导致instance变量在构造函数执行完毕之前就被设置为非null值,使得另一个线程可能会得到一个尚未完全构造好的实例。为了避免这个问题,instance变量应该被声明为volatile,以防止指令重排序。

优化代码

public class Singleton {
  private static volatile Singleton instance; // 添加volatile关键字防止指令重排序

  private Singleton() {} // 私有构造函数防止外部实例化

  static Singleton getInstance() {
    if (instance == null) {
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();
      }
    }
    return instance;
  }
}

可以看出,Java中单例代码不多,细节不少,稍不注意可能踩一坑。

对比Go中sync.Once的封装,为我们避免了多线程下的种种问题,为单例的实现提供了便利的帮助。

相关文章

  • 单例模式示例

    所谓的单例即类在创建对象时只创建一个对象。示例1 执行结果: 如果使用示例1来创建Person 类的对象,那么将会...

  • 设计模式 - 单例模式(懒汉、饿汉、双检锁、静态内部类)

    什么叫单例模式 保证某个应用程序的某个示例有且只有示例 单例模式的应用场景 需求场景的对象只需要存在一份就足够,例...

  • 单例模式(Go)

    单例模式是23种面向对象的设计模式之一。在实际应用中,涉及到配置或资源管理的对象,都应该考虑单例模式。广义上讲,只...

  • Go单例模式

    单例模式回顾 以前在做java的时候,经常会用到设计模式,如单例模式、工厂模式、观察者模式等。其实设计模式和语言无...

  • Go语言设计模式(1)单例模式

    Go语言设计模式(1)单例模式 单例模式的定义 个人认为单例模式是23种设计模式中最简单也最好理解的一种,定义如下...

  • [iOS Swift] Singleton——单例模式的使用与理

    单例模式属于创建型的设计模式。它提供了一种创建对象的最佳方式。 示例代码: 使用方式: 理解 Swift的单行单例...

  • 单例模式

    一、概述   单例模式目的是维护系统中全局唯一的实例化对象,并对外提供全局访问的方法。 二、示例   单例模式分为...

  • Go语言之单例

    单例模式是比较常用的设计模式,现在用Go简单的实现一个单例 上述代码可见,打印出来的对象都同一个,也就是说此对象只...

  • Go语言:单例模式

    转载至 https://www.yuque.com/docs/share/17c247e8-961d-4cb1-a...

  • Go的单例模式

网友评论

      本文标题:【Go - 单例示例】

      本文链接:https://www.haomeiwen.com/subject/kspuhjtx.html