欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C#中的五种单例模式(SIngleton)

程序员文章站 2022-07-14 09:06:33
...

1. 写法一(只适用于单线程环境)

	public sealed class Singleton1
	{
		private Singleton1(){}
		private static Singleton1 instance = null;
		public static Singleton1 Instance
		{
			get
			{
				if(instance == null)
					instance = new Singleton1();
					
				return instance;
			}
		}
	}

写法一中的代码再多线程中会有问题。若两个线程同时判读instance是否为null,且instance没有创建时,那么两个线程都会创建一个实例,此时已符合单例模式的要求了。

  1. 写法二(虽然能在多线程环境中工作但效率不高)
	public sealed class Singleton2
	{
		private Singleton2(){}

		private static readonly object syncObj = new Object();

		private static Singleton2 instance = null;
		public static Singleton2 Instance
		{
			get
			{
				lock(syncObj)
				{
					if(instance == null)
						instance = new Singleton1();
				}
				return instance;
			}
		}
	}

我们还是假设有两个线程同时想创建一个实例。由于在一个时刻只有一个线程能得到同步锁,当第一个线程加上锁时,第二个线程只能等待。当第一个线程发现实例还没有创建时,它创建出一个实例。接着第一个线程释 放同步锁,此时第二个线程可以加上同步锁,并运行接下来的代码。
这个时候由于实例已经被第一个线程创建出来了,第二个线程就不会重复创建实例了,这样就保证了我们在多线程环境中也只能得到一个实例。但我们每次通过Instance获取实例时,都会试图加上一个同步锁,而加锁是一个十分耗时的操作,在没有必要的时候我们应该尽量避免。

  1. 写法三(加同步锁前后两次判断实例是否已存在)
public sealed class Singleton3
	{
		private Singleton3(){}

		private static readonly object syncObj = new Object();

		private static Singleton3 instance = null;
		public static Singleton3 Instance
		{
			get
			{
				if(instance == null)
				{
					lock(syncObj)
					{
						if(instance == null)
							instance = new Singleton1();
					}
				}
				return instance;
			}
		}
	}

Singleton3中只有当 instance为nul即没有创建时,需要加锁操作。当
Instance已经创建出来之后,则无须加锁。因为只在第一次的时候 Instance
为nul,因此只在第一次试图创建实例的时候需要加锁。这样 Singleton3的
时间效率比 Singleton2要好很多。
Singleton3用加锁机制来确保在多线程环境下只创建一个实例,并且用两个i判断来提高效率。这样的代码实现起来比较复杂,容易出错,我们还有更加优秀的解法。
(至于为什么要两个if,因为可能会有多条线程进入第一个if,而第二个if只有第一条线程能进去)

  1. 写法四(推荐1:利用静态构造函数)
    C#的语法中有一个函数能够确保只调用一次,那就是静态构造函数,
    我们可以利用C#这个特性实现单例模式如下
public sealed class Singleton4
{
	private Singleton4(){}
	private static Singleton4 instance = new Singleton4();
	public static Singleton4 Instance
	{
		get
		{
			return instance;
		}
	}
}

Singleton4的实现代码非常简洁。我们在初始化静态变量 Instance的时候创建一个实例。由于C#是在调用静态构造函数时初始化静态变量 , .NET运行时能够确保只调用一次静态构造函数,这样我们就能够保证只初始化一次Instance。
C#中调用静态构造函数的时机不是由程序员掌控的,而是当NET运行时发现第一次使用一个类型的时候自动调用该类型的静态构造函数。因此在 Singleton4中,实例 Instance并不是第一次调用属性 Singleton4 Instance的时候创建,而是在第一次用到 Singleton4的时候就会被创建。假设我们在Singleton4中添加一个静态方法,调用该静态函数是不需要创建一个实例的,但如果按照 Singleton4的方式实现单例模式,则仍然会过早地创建实例,从而降低内存的使用效率。

  1. 写法五(推荐2:实现按需创造实例)
public sealed class Singleton5
{
	private Singleton5(){}
	public static Singleton5 Instance
	{
		get
		{
			return Nested.instance;
		}
	}
	class Nested
	{
		static Nested(){}
		internal static readonly Singleton5 instance = new Singleton5();
	}
}

在上述 Singleton5的代码中,我们在内部定义了一个私有类型 Nested当第一次用到这个嵌套类型的时候,会调用静态构造函数创建 Singleton5的实例 Instance。类型 Nested只在属性 Singletons Instance中被用到,由于其私有属性他人无法使用 Nested类型。因此当我们第一次试图通过属性Singleton5 Instance得到 Singleton5的实例时,会自动调用 Nested的静态构造函数创建实例 instance。如果我们不调用属性 Singletons Instance,那么就不会触发NET运行时调用 Nested,也不会创建实例,这样就真正做到了按需创建。

*注:本文参考自《剑指offer》何海涛著