一、第一种实现,行为依赖编译器
class Singleton
{
public:
static Singleton* Get()
{
static Singleton s;
return &s;
}
private:
Singleton()
{
printf("hello");
}
};
上述实现方式的线程安全性依赖编译器。如果编译器实现了“静态局部对象初始化的线程安全”那么它是线程安全的,否则不是。visual studio 2015及以后实现了这个特性。关联文档如下:
关于“静态局部对象初始化的线程安全”的文档如下:(1)在visual studio 2015 的更新说明文档中,Thread-Safe "Magic" Statics Static local variables are now initialized in a thread-safe way, eliminating the need for manual synchronization. Only initialization is thread-safe, use of static local variables by multiple threads must still be manually synchronized. The thread-safe statics feature can be disabled by using the /Zc:threadSafeInit- flag to avoid taking a dependency on the CRT. (C++11)。(2)在“ISOIEC 14882 2011”的第6.7.4节的描述为“If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.”
我这里有个测试结果供参考,上述代码在visual studio 2012和visual studio 2015生成的汇编文件不同,如下图:
二、第二种实现,简单实用,但也有缺点
class Singleton
{
public:
static Singleton* Get()
{
return &s;
}
private:
Singleton()
{
printf("hello");
}
private:
static Singleton s;
};
Singleton Singleton::s;
我经常使用这种实现方法。因为简单又实用。是线程安全的。缺点就是不能控制初始化和销毁的时间。
三、第三种实现,有优点也有缺点
class Singleton
{
public:
static Singleton* Get()
{
lock_guard lock(mut_);
if (ins_ == nullptr)
{
ins_ = new Singleton();
}
return ins_;
}
static void Destroy()
{
lock_guard lock(mut_);
if (ins_ != nullptr)
{
delete ins_;
ins_ = nullptr;
}
}
private:
Singleton()
{
printf("hello\n");
};
private:
static Singleton* ins_;
static mutex mut_;
};
Singleton* Singleton::ins_;
mutex Singleton::mut_;
优点是:能延迟初始化,能主动销毁。显式的进行加锁,明了的表明这是线程安全你的。缺点是:代码显得臃肿。有的人会在乎这个锁,毕竟初始化完成后就不需要再进行多线程的互斥了,但这里每次获取实例都加一次锁。
四、第四种实现,双检锁方式
class Singleton
{
public:
static Singleton* Get()
{
if (ins_ == nullptr)
{
lock_guard lock(mut_);
if (ins_ == nullptr)
{
ins_ = new Singleton();
}
}
return ins_;
}
static void Destroy()
{
lock_guard lock(mut_);
if (ins_)
{
delete ins_;
ins_ = nullptr;
}
}
private:
Singleton()
{
printf("hello\n");
};
private:
volatile static atomic ins_;
static mutex mut_;
};
volatile atomic Singleton::ins_ = nullptr;
mutex Singleton::mut_;
缺点:先说说它的背景:C#.net库使用双检锁实现延迟加载类(即双检锁的单例类);Java SE5之前因为volatile语义没有被JDK开发者正确实现,导致使用早期JDK的时,双检锁的实现不能保证线程安全。再说说它的缺点:因为正确实现双检锁,要求使用者对编译优化、处理器的优化处理有精准的理解,这其实是硬伤,我们不能提倡在刀尖起舞,尤其涉及多线程编程时。包括我的这个示例代码也有些可讨论之处。我使用了volatile 声明ins_为易变的,想禁止编译器把第一个if语句当作冗余读删除,其实这两次读在锁两边,我还是倾向于认为即使不加这个volatile声明,一般编译器也不会激进到做这种优化,但我不敢保证(毕竟原则上冗余读优化与多线程共享变量保护是两回事),这种模棱两可真是可怕
优点:延迟初始化、主动销毁、性能高。
五、第五种实现,好不好全凭你的感觉
class Singleton
{
public:
static Singleton* Get()
{
std::call_once(oc_,[]() { ins_ = new Singleton(); });
return ins_;
}
private:
Singleton()
{
printf("hello\n");
};
private:
static Singleton* ins_;
static std::once_flag oc_;
};
Singleton* Singleton::ins_ = nullptr;
std::once_flag Singleton::oc_;
用不用这种方法全凭每个人的感觉吧。不算太臃肿,用了一些C++的新特性,也许你不喜欢这些特性。有个问题值得注意网上有人把once_flag的变量声明为Get()的静态局部变量,如果编译器不保证静态局部变量的线程安全性,岂不是多线程不安全了。
六、第六种实现,C#的单例,在C#中就这么写就行
class Singleton
{
public static Singleton Instance
{
get
{
return _lazy_ins.Value;
}
}
private Singleton()
{
Console.WriteLine("hello");
}
private static readonly Lazy _lazy_ins = new Lazy(() => { return new Singleton(); });
}
简单的不要不要的,安全的不要不要的。借助了.net类库中的类Lazy。默认情况下Lazy内部的实现为双检锁模式。可以通过传参指定其他的模式,不过最优的选择还是默认的。