【设计模式】单例模式
1、概述
单例模式:某一个类在系统中只需要有一个实例对象,而且对象是由这个类自行实例化并提供给系统其它地方使用。单例模式属于一种创建型设计模式。从概述中,我们可以总结三个要点:
- 单例类只能有一个实例,即使是多线程运行环境下;
- 单例类的实例一定是单例类自身创建,而不是在单例类外部用其它方式如new方式创建;
- 单例类需要提供一个方法向整个系统提供这个实例对象。
2、单例模式实现思路
首先,我们先创建一个简单的类。
public class SingletonFirst
{
public SingletonFirst()
{
Console.WriteLine("构造函数被调用一次!!!");
}
}
static void Main(string[] args)
{
SingletonFirst singleton1 = new SingletonFirst();
SingletonFirst singleton2 = new SingletonFirst();
Console.WriteLine($"判断实例地址是否一致? {singleton1.Equals(singleton2)}");
}
此时,输出结果是False。
下面我们构造一个单例类:
- 构造函数定义为私有,防止外部创建实例;
- 提供一个方法向整个系统提供这个实例对象。
public class SingletonFirst
{
/// <summary>
/// 构造函数定义为私有,防止外部创建实例;
/// </summary>
private SingletonFirst()
{
Console.WriteLine("构造函数被调用一次!!!");
}
private static SingletonFirst _Instance = null;
/// <summary>
/// 提供一个方法向整个系统提供这个实例对象
/// </summary>
/// <returns></returns>
public static SingletonFirst CreateSingleton()
{
if (_Instance == null)
_Instance = new SingletonFirst();
return _Instance;
}
}
static void Main(string[] args)
{
SingletonFirst singleton1 = SingletonFirst.CreateSingleton();
SingletonFirst singleton2 = SingletonFirst.CreateSingleton();
Console.WriteLine($"判断实例地址是否一致? {singleton1.Equals(singleton2)}");
}
此时,输出结果是True。
但是上面的写法是线程不安全的。单例模式需要注意的地方就是要写出一个能保证在多线程环境下也能保证实例唯一性的单例。
这里,我们改造一下类,使用多线程查看一下输出的结果。
public class SingletonFirst
{
/// <summary>
/// 构造函数定义为私有,防止外部创建实例;
/// </summary>
private SingletonFirst()
{
int result = 0;
for (int i = 0; i < 10000; i++)
{
result += i;
}
Thread.Sleep(2000);
Console.WriteLine("构造函数被调用一次!!!");
}
private static SingletonFirst _Instance = null;
/// <summary>
/// 提供一个方法向整个系统提供这个实例对象
/// </summary>
/// <returns></returns>
public static SingletonFirst CreateSingleton()
{
if (_Instance == null)
_Instance = new SingletonFirst();
return _Instance;
}
}
static void Main(string[] args)
{
//SingletonFirst singleton1 = SingletonFirst.CreateSingleton();
//SingletonFirst singleton2 = SingletonFirst.CreateSingleton();
SingletonFirst singleton1 = null;
SingletonFirst singleton2 = null;
Task.Run(() =>
{
singleton1 = SingletonFirst.CreateSingleton();
});
Task.Run(() =>
{
singleton2 = SingletonFirst.CreateSingleton();
});
Thread.Sleep(3000);
Console.WriteLine($"判断实例地址是否一致? {singleton1.Equals(singleton2)}");
}
此时,输出结果是False。
线程不安全解决方法:
给线程加锁,就可以解决上述问题。
public class SingletonFirst
{
/// <summary>
/// 构造函数定义为私有,防止外部创建实例;
/// </summary>
private SingletonFirst()
{
int result = 0;
for (int i = 0; i < 10000; i++)
{
result += i;
}
Thread.Sleep(2000);
Console.WriteLine("构造函数被调用一次!!!");
}
private static SingletonFirst _Instance = null;
private static readonly object singletonFirstLock = new object();
/// <summary>
/// 提供一个方法向整个系统提供这个实例对象
/// </summary>
/// <returns></returns>
public static SingletonFirst CreateSingleton()
{
lock (singletonFirstLock)
{
if (_Instance == null)
_Instance = new SingletonFirst();
}
return _Instance;
}
}
此时,输出结果是True。
这样一来,确实线程安全了,但是又带来了另一个问题:程序的性能极大的降低了,高并发下多个线程去获取这个实例,现在却要排队。
那么有没有一种写法,可以同时兼顾到效率和线程安全两方面,这个就是我们下面将要介绍的double-check的方式。
double-check:
public class SingletonFirst
{
/// <summary>
/// 构造函数定义为私有,防止外部创建实例;
/// </summary>
private SingletonFirst()
{
int result = 0;
for (int i = 0; i < 10000; i++)
{
result += i;
}
Thread.Sleep(2000);
Console.WriteLine("构造函数被调用一次!!!");
}
private static SingletonFirst _Instance = null;
private static readonly object singletonFirstLock = new object();
/// <summary>
/// 提供一个方法向整个系统提供这个实例对象
/// </summary>
/// <returns></returns>
public static SingletonFirst CreateSingleton()
{
if (_Instance == null)
{
lock (singletonFirstLock)
{
if (_Instance == null)
_Instance = new SingletonFirst();
}
}
return _Instance;
}
}
这种单例的写法做了两次 if (_Instance == null)的判断,因此被称为double-check的方式。
- 第一次check为了提高访问性能。因为一旦实例被创建,后面线程的所有的check都为假,不需要执行锁了。
- 第二次check是为了线程安全,确保多线程环境下只生成一个实例。
3、懒汉模式和饿汉模式
懒汉模式
上述介绍的模式就是懒汉模式,顾名思义,这个类很懒,只要别人不找它要实例,它都懒得创建。
饿汉模式
在初始化时,我们就创建了唯一的实例,即便这个实例后面并不会被使用。
下面我们介绍饿汉模式两种实现方式。
静态构造函数
public class SingletonThird
{
/// <summary>
/// 构造函数定义为私有,防止外部创建实例;
/// </summary>
private SingletonThird()
{
int result = 0;
for (int i = 0; i < 10000; i++)
{
result += i;
}
Thread.Sleep(2000);
Console.WriteLine("构造函数被调用一次!!!");
}
private static SingletonThird _Instance = null;
/// <summary>
/// 静态构造函数,在程序第一次使用这个类型之前,由CLR调用且只调用一次,适合做初始化
/// </summary>
static SingletonThird()
{
_Instance = new SingletonThird();
}
/// <summary>
/// 提供一个方法向整个系统提供这个实例对象
/// </summary>
/// <returns></returns>
public static SingletonThird CreateSingleton()
{
return _Instance;
}
}
静态字段
public class SingletonSecond
{
/// <summary>
/// 构造函数定义为私有,防止外部创建实例;
/// </summary>
private SingletonSecond()
{
int result = 0;
for (int i = 0; i < 10000; i++)
{
result += i;
}
Thread.Sleep(2000);
Console.WriteLine("构造函数被调用一次!!!");
}
/// <summary>
/// 静态字段,在程序第一次使用这个类型之前,由CLR调用且只调用一次,适合做初始化
/// </summary>
private static SingletonSecond _Instance = new SingletonSecond();
/// <summary>
/// 提供一个方法向整个系统提供这个实例对象
/// </summary>
/// <returns></returns>
public static SingletonSecond CreateSingleton()
{
return _Instance;
}
}