在纯粹的面向对象语言中例如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实现单例模式的时候只需要用小写的全局变量就可以了。