单例模式由浅入深(C# 版)
有时候,我们希望某类只有一个实例,这样的好处是:
1.可以实现数据共享
2.避免大量的创建销毁实例的操作,提高性能
为了实现单例模式,通常做法是:
1.将构造函数私有化,避免外部直接new对象
2.对外提供一个方法来返回一个单例对象实例
最简单的代码像这样:
class SingleTest
{
private static SingleTest singleTest;
public int Cnt { get; private set; }
private SingleTest() { }
public static SingleTest GetSingleObject()
{
if(singleTest is null)
{
singleTest = new SingleTest();
Console.WriteLine("实例被创建");
}
return singleTest;
}
public void Increase()
{
Cnt++;
}
}
这样的话,外部可以通过静态方法获取单例实例,多次获取到的实例其实是同一个实例
static void Main(string[] args)
{
SingleTest singleTest = SingleTest.GetSingleObject();
SingleTest singleTest2 = SingleTest.GetSingleObject();
Console.WriteLine($"singleTest == singleTest2 ? {singleTest == singleTest2}"); //True
singleTest2.Increase();
Console.WriteLine(singleTest.Cnt); // 1
}
以上的单例模式的实现被称为懒汉式,所谓懒,就是指只有调用了GetSingleObject()方法,才会去创建实例,不调用GetSingleObject()方法是不可能创建出实例的
以上的代码存在线程不安全的问题,如果多线程同时调用GetSingleObject()方法,会产生多个实例
class Program
{
private static object lockObject = new();
static void Main(string[] args)
{
List<Task> tasks = new();
for (int i = 0; i < 100; i++)
{
tasks.Add(Task.Run(() =>
{
SingleTest singleTest = SingleTest.GetSingleObject();
lock (lockObject)
{
singleTest.Increase();
}
}));
}
Task.WaitAll(tasks.ToArray());
SingleTest singleTest = SingleTest.GetSingleObject();
Console.WriteLine(singleTest.Cnt);
}
}
运行以上代码发现"实例被创建"打印了多次
为了解决以上问题,可以使用双重校验加锁的方式,增加一个属性用于加锁并且修改GetSingleObject方法
private static object lockObject = new();
public static SingleTest GetSingleObject()
{
if(singleTest is null)
{
lock (lockObject)
{
if (singleTest is null)
{
singleTest = new SingleTest();
Console.WriteLine("实例被创建");
}
}
}
return singleTest;
}
其中lock加锁保证同时只能有一个线程进入,这样就不会创建多个单例
lock外部的if判断是为了提高效率,因为如果去掉这个if,所有线程都需要等lockObject锁,但是这样的等待不一定的必须的,因为当singleTest不为null时完全不需要等锁,因此加上这个if语句来提高效率
lock内部的if判断显然是必须的,因此并不是线程一旦获得了锁就要创建实例,如果去掉,还是会造成创建多个实例的问题
单例模式(懒汉式线程安全)完整代码是这样的:
class SingleTest
{
private static SingleTest singleTest;
private static object lockObject = new();
public int Cnt { get; private set; }
private SingleTest() { }
public static SingleTest GetSingleObject()
{
if(singleTest is null)
{
lock (lockObject)
{
if (singleTest is null)
{
singleTest = new SingleTest();
Console.WriteLine("实例被创建");
}
}
}
return singleTest;
}
public void Increase()
{
Cnt++;
}
}
还有一种被称为饿汉式的单例模式,原理是利用静态构造方法只调用一次或静态属性初始化时赋值一次的性质来保证实例只有一个
1.利用静态构造方法只调用一次的性质实现饿汉式的单例模式
class SingleTest
{
private static SingleTest singleTest;
public int Cnt { get; private set; }
static SingleTest()
{
singleTest = new SingleTest();
Console.WriteLine("实例被创建");
}
public static SingleTest GetSingleObject()
{
return singleTest;
}
public void Increase()
{
Cnt++;
}
}
2.利用静态属性初始化时赋值一次的性质实现饿汉式的单例模式
class SingleTest
{
private static SingleTest singleTest = new();
public int Cnt { get; private set; }
private SingleTest()
{
}
public static SingleTest GetSingleObject()
{
return singleTest;
}
public void Increase()
{
Cnt++;
}
}
所谓“饿汉式”的单例模式,意思是即使不调用GetSingleObject方法,SingleTest初始化时就会把实例创建好,展现出一种迫不及待的感觉,因此称为“饿汉式”
显然,饿汉式的单例模式是线程安全的