有关Go中单例模式的理解

本文对比了Java/C#与Golang实现单例模式的不同,详细解析了Golang如何利用小写全局变量替代static关键字,实现懒汉模式、饿汉模式及双重检查机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在纯粹的面向对象语言中例如java/C#,在实现单例模式的时候都要用到static的关键字,在golang中是没有static关键字的,看到网上很多人用go实现单例模式并没有用static,于是思考了一下原因。

用C#实现单例模式

懒汉模式

/// <summary>
    /// 单例模式的实现
    /// </summary>
    public class Singleton
    {
        // 定义一个静态变量来保存类的实例
        private static Singleton uniqueInstance;

        // 定义私有构造函数,使外界不能创建该类实例
        private Singleton()
        {
        }

        /// <summary>
        /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            // 如果类的实例不存在则创建,否则直接返回
            if (uniqueInstance == null)
            {
                uniqueInstance = new Singleton();
            }
            return uniqueInstance;
        }
    }

线程安全的

/// <summary>
    /// 单例模式的实现
    /// </summary>
    public class Singleton
    {
        // 定义一个静态变量来保存类的实例
        private static Singleton uniqueInstance;

        // 定义一个标识确保线程同步
        private static readonly object locker = new object();

        // 定义私有构造函数,使外界不能创建该类实例
        private Singleton()
        {
        }

        /// <summary>
        /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            // 当第一个线程运行到这里时,此时会对locker对象 "加锁",
            // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
            // lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
            lock (locker)
            {
                // 如果类的实例不存在则创建,否则直接返回
                if (uniqueInstance == null)
                {
                    uniqueInstance = new Singleton();
                }
            }

            return uniqueInstance;
        }
    }

双重检查机制

/// <summary>
    /// 单例模式的实现
    /// </summary>
    public class Singleton
    {
        // 定义一个静态变量来保存类的实例
        private static Singleton uniqueInstance;

        // 定义一个标识确保线程同步
        private static readonly object locker = new object();

        // 定义私有构造函数,使外界不能创建该类实例
        private Singleton()
        {
        }

        /// <summary>
        /// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
        /// </summary>
        /// <returns></returns>
        public static Singleton GetInstance()
        {
            // 当第一个线程运行到这里时,此时会对locker对象 "加锁",
            // 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
            // lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
            // 双重锁定只需要一句判断就可以了
            if (uniqueInstance == null)
            {
                lock (locker)
                {
                    // 如果类的实例不存在则创建,否则直接返回
                    if (uniqueInstance == null)
                    {
                        uniqueInstance = new Singleton();
                    }
                }
            }
            return uniqueInstance;
        }
    }

饿汉模式

//这种模式的特点是自己主动实例。
public sealed class Singleton
{
    private static readonly Singleton instance=new Singleton();
 
    private Singleton()
    {
    }

    public static Singleton GetInstance()
    {
        return instance;
    }
}

用go实现单例模式:

懒汉模式


type SingleObject struct {
	Count int
}
 
var singleObj *SingleObject
 
func GetInstance1() *SingleObject {
	if singleObj == nil {
		singleObj = new(SingleObject)
	}
	return singleObj
}

饿汉模式


var singleObj *SingleObject
func init() {
	singleObj = new(SingleObject)
}
 
func GetInstance2() *SingleObject {
	return singleObj
}

双重检查机制

var lock *sync.Mutex = &sync.Mutex{}
func GetInstance3() *SingleObject {
	if singleObj == nil {
		lock.Lock()
		defer lock.Unlock()
		singleObj = new(SingleObject)
	}
	return singleObj
}

从上面的的两种对比可以看出,go在定义变量保存实例的时候是没有用static的,而是用的全局变量,只不过是小写即对外部不可见的全局变量。

在纯面向对象的语言中变量可以细分如下:

分类细则:变量按作用范围划分分为成员变量和局部变量

成员变量按调用方式划分分为实例属性与类属性

成员变量只能通过对象来访问  
注意: 成员变量不能离开类, 离开类之后就不是成员变量 成员变量不能在定义的同时进行初始化  
存储: 堆(当前对象对应的堆的存储空间中)  
存储在堆中的数据, 不会被自动释放, 只能程序员手动释放

局部变量按定义位置划分分为形参,方法局部变量,代码块局部变量

写在函数或者代码块中的变量, 我们称之为局部变量  
作用域: 从定义的那一行开始, 一直到遇到大括号或者return  
局部变量可以先定义再初始化, 也可以定义的同时初始化  
存储 : 栈  
存储在栈中的数据有一个特点, 系统会自动给我们释放  

重点:在纯面向对象中是没有全局变量的,但是在go语言中是有全局变量的而且还有被访问限制

再来看一下 static全局变量与普通的全局变量有什么区别 ?

  全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。

  全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。

  这两者的区别在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。 

可以看出go语言中小写的全局变量与静态变量并没有区别,这样就可以将go语言中的小写的全局变量看做是静态全局变量

因此小写的全局变量就可以看做是面向对象语言中static类型的成员变量

而面向对象语言中,实现单例的静态方法要加static是因为静态字段只有静态字段才能访问。所以用golang实现单例模式的时候只需要用小写的全局变量就可以了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值