go

golang单例模式

介绍

单例模式属于创建型模式,它提供了一种创建对象的最佳方式。在应用程序中,有些情况下需要确保某个类只有一个实例对象,比如数据库连接池、线程池、垃圾回收器、日志记录器等。单例模式可以确保这些类只被实例化一次,并提供一个全局访问点,使其他对象可以方便地访问该实例。

file

特点

  1. 单例类只有一个实例对象;
  2. 该单例对象必须由单例类自行创建;
  3. 单例类对外提供一个访问该单例的全局访问点。

优点

  1. 在内存里只有一个实例,调用时复用该单例对象即可,减少了内存的开销,尤其是频繁的创建和销毁实例。
  2. 避免了对资源的多重占用。

缺点

  1. 全局状态和共享状态:单例模式引入全局状态和共享状态,可能会增加代码的复杂性和耦合性。修改单例类的行为可能会对整个系统产生影响。
  2. 难以扩展和测试:由于单例模式将对象的创建和访问耦合在一起,当需要扩展或修改单例类时,可能会面临困难,不利于单元测试和代码维护。
  3. 违反单一职责原则:单例模式往往承担了太多的职责,既负责创建实例,又负责管理全局状态和提供全局访问点,导致单例类的职责不够单一,违反了单一职责原则。

使用场景

  1. 资源共享:当多个对象需要共享同一个资源(如数据库连接池、线程池)时,使用单例模式可以确保只有一个实例被创建和共享,避免资源的重复创建和浪费。
  2. 对象缓存:在需要缓存对象的场景下,使用单例模式可以确保只有一个缓存实例存在,提高缓存的效率和一致性。
  3. 配置信息:当系统中的某些配置信息需要被多个对象共享和访问时,可以使用单例模式来管理和提供该配置信息的实例。比如唯一序列号。
  4. 日志记录:单例模式可以用来创建全局的日志记录器,以确保在整个应用程序中只有一个实例记录日志,方便进行统一的日志管理和记录。
  5. GUI应用程序中的窗口管理:在GUI应用程序中,通常需要管理和控制多个窗口的状态和行为。使用单例模式可以创建一个窗口管理器实例,统一管理窗口的创建、销毁和状态。
  6. 计数器或计时器:在需要对某个计数器或计时器进行全局控制和访问的场景下,可以使用单例模式来创建该实例,并确保只有一个实例存在。比如web中的计数器。

实现方法

实现方法有两种:懒汉式和饿汉式。这两种的主要区别在于实例的创建时机和线程的安全性。

懒汉式

懒汉式(Lazy Initialization):是指在首次访问时才创建实例。具体来说,当第一次调用获取实例的方法时,判断实例是否创建,如果没有则创建一个实例并返回。如果实例已经存在,则直接返回现有的实例。

优点:

  • 延迟加载:只有在需要时才会创建实例,可以节省系统资源。
  • 线程安全(通过加锁):可以通过加锁机制确保在多线程环境下只有一个线程创建实例。

缺点:

  • 性能受影响:由于需要在获取实例时进行判断和可能的加锁操作,因此可能会对性能产生一定的影响。
  • 实现复杂度增加:为了实现线程安全,需要考虑加锁的逻辑,增加了实现的复杂度。

不加锁实现

这种方式是会导致线程不安全的。在高并发的时候会有多个线程同时调用这个方法,那么都会检测instance为nil,这样就会导致创建多个对象,所以这种方法是不推荐的。

注意这里的singleton必须是小写的,如果是大写的,其他包就可以通过s := &singleton{}来创建多个实例,单例模式就没有意义了。

package singleton

type singleton struct {}

var instance *singleton

func GetInstance() *singleton {
    if instance == nil {
        instance = &singleton{}
    }
    return instance
}

整个方法加锁实现

使用Sync.Mutex进行加锁,保证线程安全,这种写法有个最大的缺点就是每次调用该方法时都需要进行锁操作,在性能上相对不高效。

package singleton

import "sync"

type singleton struct {}

var instance *singleton
var lock sync.Mutex // 锁对象

func GetInstance() *singleton {
    lock.Lock()
    defer lock.Unlock()
    if instance == nil{
        instance = &singleton{}
        //instance = new(singleton)
    }
    return instance
}

创建实例时加锁实现

虽然加了锁,但多线程还是会创建出多实例,也不推荐。

package singleton

import "sync"

type singleton struct {}

var instance *singleton
var lock sync.Mutex

func GetInstance() *singleton {
    if instance == nil{
        lock.Lock()
        instance = &singleton{}
        lock.Unlock()
    }
    return instance
}

双重检查加锁实现

只有当对象未初始化的时候,才会有加锁和减锁的操作。但是又出现了另一个问题:每一次访问都要检查两次。

package singleton

import "sync"

type singleton struct {}

var instance *singleton
var lock sync.Mutex

func GetInstance() *singleton {
    if instance == nil{
        lock.Lock()
        if instance == nil{
            instance = &singleton{}
        }
        lock.Unlock()
    }
    return instance
}

原子操作实现

通过sync.Once的Do方法来确保创建对象的方法只执行一次,sync.Once内部本质上也是双重检查的方式,但在写法上会上面更简洁,这样就可以只创建了一个对象,这种方法是推荐的。

package singleton

import "sync"

type singleton struct {
}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })
    return instance
}

饿汉式

饿汉式(Eager Initialization):是指在类加载时就创建实例,无论是否使用该实例。在类加载过程中,实例就会被创建,并且在整个应用程序生命周期中都存在。
优点:

  • 简单直观:实例在类加载时就被创建,使用起来非常简单。
  • 无需加锁,更安全。
    缺点:
  • 资源浪费:无论是否使用该实例,都会在应用程序启动时创建实例,可能会浪费一些系统资源,启动速度慢。
  • 不支持延迟加载:由于实例在类加载时就被创建,无法实现延迟加载的特性。

全局变量实现

package singleton

type singleton struct {}

var instance *singleton

func GetInstance() *singleton{
 return instance
}

init函数实现

init函数在包被加载时自动执行,因此在应用程序启动时就会创建实例并赋值给instance变量。

package singleton

type singleton struct{}

var instance *singleton

func init() {
    instance = &singleton{}
}

func GetInstance() *singleton {
    return instance
}

两种方法都可以实现,没有什么区别。

分类: go
0 0 投票数
文章评分
订阅评论
提醒
guest

0 评论
最旧
最新 最多投票
内联反馈
查看所有评论

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部
0
希望看到您的想法,请您发表评论x