在C#中,我们对于只会生成一个实例的类对其实现单例模式。
首先我们实现的是适用于单线程环境的单例模式。要注意我们必须将构造函数设为私有,防止其它类创建实例,代码如下:
using System;
namespace ConsoleApp1
{
public sealed class Singleton
{
private Singleton()
{
}
private static Singleton _instance = null;
public static Singleton Instance
{
get
{
if(_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
}
class Program
{
static void Main(string[] args)
{
}
}
}
只有当_instance为null的时候我们才会创建一个实例来避免重复创建,但是这种写法在单线程的时候工作正常,多线程时就会出现问题。加入我们有两个线程分别想要同时运行判断instance是否为null,并且instance在这个时刻确实为null,那么两个线程就都会创建一个实例,因此我们需要在程序中加上一个同步锁,代码如下:
using System;
namespace ConsoleApp1
{
public sealed class Singleton
{
private Singleton()
{
}
private static readonly object syncObj = new object();
private static Singleton _instance = null;
public static Singleton Instance
{
get
{
lock(syncObj)
{
if (_instance == null)
{
_instance = new Singleton();
}
}
return _instance;
}
}
}
class Program
{
static void Main(string[] args)
{
}
}
}
当我们使用这种写法创建实例时就能够避免当两个线程同时创建实例时的问题。因为当想要创建实例的时候,只有一个线程得到了同步锁,另一个线程等待。第一个线程完成后,同步锁释放,并且单例创建完成,第二个线程获得锁之后不会再去创建单例,而是直接使用第一个线程锁创建的单例,这样就解决了多线程所带来的问题。
而这种写法导致的问题是效率低下,因为加锁是一个耗时的操作,而这种写法在每一次获取单例的时候都会进行加锁,这是应该避免的。这一改进很简单,我们只要将加锁的步骤放到判断instance为null的时候就行了,代码如下:
using System;
namespace ConsoleApp1
{
public sealed class Singleton
{
private Singleton()
{
}
private static readonly object syncObj = new object();
private static Singleton _instance = null;
public static Singleton Instance
{
get
{
if (_instance == null)
{
lock (syncObj)
{
if(_instance == null)
{
_instance = new Singleton();
}
}
}
return _instance;
}
}
}
class Program
{
static void Main(string[] args)
{
}
}
}
但是这种加锁的写法有点复杂,容易出问题,这时我们可以考虑使用C#中的静态构造函数,保证构造函数仅调用一次,写法如下:
using System;
namespace ConsoleApp1
{
public sealed class Singleton
{
private Singleton()
{
}
private static Singleton _instance = new Singleton();
public static Singleton Instance
{
get
{
if (_instance == null)
{
_instance = new Singleton();
}
return _instance;
}
}
}
class Program
{
static void Main(string[] args)
{
}
}
}
我们在初始化static变量_instance的时候创建实例,而因为变量为static,会在调用static构造函数之前执行构造,因此保证仅调用一次。但是这一段代码的问题是,如果我们在该类中存在一个static函数或其他static变量,如果我们使用到了那些static内容而非static的_instance,那么这个_isntance就会过早的创建,进而降低效率。
我们使用了一种嵌套类来解决这一问题:
using System;
namespace ConsoleApp1
{
public sealed class Singleton
{
private Singleton()
{
}
public static Singleton Instance
{
get
{
return Nested._instance;
}
}
private class Nested
{
static Nested()
{
}
internal static readonly Singleton _instance = new Singleton();
}
}
class Program
{
static void Main(string[] args)
{
}
}
}
我们在内部定义了私有类型Nested,当第一次获取单例的时候,我们会去获得Nested内部的_instance,而要想获取就会调用Nested的静态构造函数。由于Nested为私有类,我们仅仅会在这里有唯一的一次调用,这样就防止了提前构造的发生。