一、简介
本篇文章将介绍如何使用单例模式,也就是类的实例化,在整个项目的生命周期内,只实例化一次。在单例模式中,往往可以看到如SourceCode.cs:这样的结构的。
SourceCode.cs:
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
我的项目中,也有类似的结构:
好了,我把 private GlobalDataStruct()的代码显示如下:
private GlobalDataStruct()
{
m_cApplicationDir = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
m_dbKrayConfig = new cKrayDBase();
m_dbPatientInfo = new cKrayDBase();
m_csStudyPatientInfo = new PatientInfo();
m_csStudySeriesInfo = new SeriesInfo();
m_csConfigFileInfo = new ConfigStructData();
m_bEnableExpFlag = true;
m_bConfigDbOpenFlag = false;
m_bPatientDbOpenFlag = false;
m_bHandPressStatus = false;
m_bFootBreakStatus = false;
m_bGenLoadStatus = false;
m_bCanLoadStatus = false;
m_bAlreadSelectCheckListStatus = false;
m_bReviewCheckFlag = false;
m_bHandCheckFlag = false;
m_bSpliceFlag = false;
m_bDRGenExpStatus = false;
m_bFluGenExpStatus = false;
m_bLoadCurCheckDeviceMode = true;
m_sStudyBodyName = "";
m_nFrameRate = 0;
m_nGrabAcqStatus = 0;
m_nSelectCheckIndex = 0;
m_strDBDriver = "Provider = Microsoft.ACE.OLEDB.12.0; Data Source =";
InitPublicInfo();
InitDeviceInfo();
}
并且,我把 public static GlobalDataStruct CreateInstance()的代码显示如下:
public static GlobalDataStruct CreateInstance()
{
//双重加锁
if (_instance == null)
{
lock (lockobj)//线程同步问题
{
if (_instance == null)
{
_instance = new GlobalDataStruct();
}
}
}
return _instance;
}
好了不用多说,你会发现SourceCode.cs和我截图的代码结构,有着异曲同工之妙。并且发现,下面的几部分基本相同
private static Singleton instance
private Singleton() { }
public static Singleton getInstance() {}
好了,下面,我先来分析这三部分的内容,然后再分析单例模式的写法。
二、单例模式的结构
主要参考(这三个链接的文章都很好):
https://www.cnblogs.com/zhili/p/SingletonPatterm.html
https://zhidao.baidu.com/question/547674677.html
https://blog.csdn.net/u012975370/article/details/62046317
单例模式的结构,往往如下:
public class Singleton {
//类的实例化:必须是private,防止Singleton类实例化时对它进行更改。且用static保留一份。
private static Singleton instance;
//默认构造函数,但是外界不能创建该类实例
private Singleton() {
}
//public给外部访问。且用static保留一份。
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
private static Singleton instance;——表示类的实例化,用static来实例化。
private Singleton() { }—— 一般来说,C#类的默认构造函数,都是public的。但是这里却用到了private。原因很简单,防止用户使用构造函数重新构造,确保生成的实例是单一的。比如我再次实例,就不行啦,如下图:
public static Singleton getInstance() {} ——定义公有方法提供一个全局访问点。
好了,单例模式的结构,我们已经基本弄明白了。接下来,看看单例模式的几种常用写法。
三、单例模式的几种写法
主要参考:
https://www.cnblogs.com/zhili/p/SingletonPatterm.html提到了单线程、多线程、加锁
https://blog.csdn.net/u012975370/article/details/62046317提到了懒汉式、饿汉式、加锁、枚举、静态内部类、容器
1、单例模式(懒汉式,),在单线程下的实现方法:
/// <summary>
/// 单例模式的实现
/// </summary>
public class Singleton
{
// 定义一个静态变量来保存类的实例
private static Singleton uniqueInstance;
// 定义私有构造函数,使外界不能创建该类实例
private Singleton()
{
}
/// <summary>
/// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
// 如果类的实例不存在则创建,否则直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
2、单例模式,在多线程下的实现方法:
方法1:简单的加锁
/// <summary>
/// 单例模式的实现
/// </summary>
public class Singleton
{
// 定义一个静态变量来保存类的实例
private static Singleton uniqueInstance;
// 定义一个标识确保线程同步
private static readonly object locker = new object();
// 定义私有构造函数,使外界不能创建该类实例
private Singleton()
{
}
/// <summary>
/// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
lock (locker)
{
// 如果类的实例不存在则创建,否则直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
}
return uniqueInstance;
}
}
方法2:双重加锁方式
与方法不同的是,双重加锁效率更高,只需要添加 if (uniqueInstance == null) {} 这行即可。
/// <summary>
/// 单例模式的实现
/// </summary>
public class Singleton
{
// 定义一个静态变量来保存类的实例
private volatile static Singleton uniqueInstance;
// 定义一个标识确保线程同步
private static readonly object locker = new object();
// 定义私有构造函数,使外界不能创建该类实例
private Singleton()
{
}
/// <summary>
/// 定义公有方法提供一个全局访问点,同时你也可以定义公有属性来提供全局访问点
/// </summary>
/// <returns></returns>
public static Singleton GetInstance()
{
// 当第一个线程运行到这里时,此时会对locker对象 "加锁",
// 当第二个线程运行该方法时,首先检测到locker对象为"加锁"状态,该线程就会挂起等待第一个线程解锁
// lock语句运行完之后(即线程运行完之后)会对该对象"解锁"
// 双重锁定只需要一句判断就可以了
if (uniqueInstance == null)
{
lock (locker)
{
// 如果类的实例不存在则创建,否则直接返回
if (uniqueInstance == null)
{
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
好了,你可能会注意到,双重加锁代码中,多了volatile 关键字,接下来分析volatile关键字。volatile关键字与共享内存有关。
四、共享内存(volatile关键字)、线程同步
线程同步:就是要保证多个线程 安全地 访问共享内存。
主要参考:
https://blog.csdn.net/u012975370/article/details/62046317
https://blog.csdn.net/lonestar555/article/details/49025169
多个线程同时访问一个变量,CLR为了效率,允许每个线程进行本地缓存,这就导致了变量的不一致性。volatile就是为了解决这个问题,volatile修饰的变量,不允许线程进行本地缓存,每个线程的读写都是直接操作在共享内存上,这就保证了变量始终具有一致性。即共享内存允许多个线程有序地读写变量,这些变量保存在共享内存上,保证了变量的一致性(比如单实例类)。
public class Singleton {
private volatile static Singleton instance = null;
public static Singleton getInstance() {
Singleton inst = instance; // 创建临时变量
if (inst == null) {
synchronized (Singleton.class) {
inst = instance;
if (inst == null) {
inst = new Singleton();
}
}
}
return inst; // 返回临时变量
}
private Singleton() {}
}
五、总结
线程同步问题,虽然可以保证安全地访问共享内存,但是效率较慢。