《C#设计模式》--01.单例模式(创建型设计模式)
·
提示:以下是本篇文章正文内容,下面案例可供参考
一、概述
保证在整个应用程序中,某个对象保证唯一性,此对象没有
单例应用场景
- Windows的Task Manager(任务管理器),无法打开第二个
- windows的Recycle Bin(回收站)
- 网站的计数器,一般也是采用单例模式来实现计数的同步
- 日志中的log对象是单例的,因为此对象初始化时就确定了log输出的格式,地址等等,确保唯一性
- 数据库连接池的设计一般也是采用单例模式
- 多线程的线程池的设计一般也是采用单例模式
- HttpApplication 也是单位例的典型应用。所有的HttpModule都共享一个HttpApplication实例
单例实现的环境
单例实现的场景,也就是单例模式面临的考验,需要解决的一些问题。
- 单线程环境
- 多线程环境
- 反射
二、单例的实现
单线程
在单线程下,单例的实现比较简单,分为懒汉式和饿汉式
饿汉式
在类加载时就初始化对象,简单但是过多的创建对象,可能会浪费内存空间
/// <summary>
/// 饿汉式
/// </summary>
public class SingleA
{
private static readonly SingleA obj = new SingleA();
private SingleA() { }
public static SingleA GetInstance(){return obj;}
}
懒汉式
还算完美,懒加载,第一次用时才创建对象
/// <summary>
/// 懒汉式
/// </summary>
public class SingleB
{
private static SingleB obj;
private SingleB()
{
}
public static SingleB GetInstance()
{
if (obj == null)
{
obj = new SingleB();
}
return obj;
}
}
单线程+多线程
如果现在多了一个多线程环境的适配,该如何实现呢?主要要解决的问题时,如何确保对象的唯一性
饿汉式
靠,完美兼容,类加载可只加载一次,所以管你有没有多线程操作呢,对象就一个
懒汉式
的确需要处理
public class SingleC
{
private volatile static SingleC obj;
private static readonly object lock_obj = new object();
private SingleC(){}
public static SingleC GetInstance()
{
/**
* 第一个if判断作用:只有obj为空的时候才会有对象不唯一的风险,
* 才有锁的必要
*/
if(obj == null)
{
lock (lock_obj)
{
/**
* 第二个if判断作用:可能第一个线程走到这里时,第二个线程刚好把obj创建出来
*/
if (obj == null)
{
/**
* 对象的创建过程经历的指令:
* 1.在内存中开辟空间
** 2.执行构造函数
* 3.把空间指向对象(对象地址)
* 在极端情况下,会出现指令重排,导致返回错误的对象,所以需要volatile关键字声明
*/
obj = new SingleC();
}
}
}
return obj;
}
}
单线程+多线程+反射
在这种环境下,确保对象的唯一性时,反射会对【单线程+多线程】模式下的单例造成破坏,无论饿汉式还是懒汉式,都会造成影响,下面以懒汉式为例提供解决方式。
public class SingleC
{
private volatile static SingleC obj;
private static readonly object lock_obj = new object();
private static bool newFlag = false;
private SingleC()
{
///防止多线程操作
lock (lock_obj)
{
/**
* GetInstance和反射都只能调用一次,谁先调用谁牛逼,但是第二次的反射就不被允许了
*/
if (!newFlag) newFlag = true;
else throw new Exception("不允许通过反射创建对象!!!");
}
}
public static SingleC GetInstance()
{
/**
* 第一个if判断作用:只有obj为空的时候才会有对象不唯一的风险,
* 才有锁的必要
*/
if(obj == null)
{
lock (lock_obj)
{
/**
* 第二个if判断作用:可能第一个线程走到这里时,第二个线程刚好把obj创建出来
*/
if (obj == null)
{
/**
* 对象的创建过程经历的指令:
* 1.在内存中开辟空间
** 2.执行构造函数
* 3.把空间指向对象(对象地址)
* 在极端情况下,会出现指令重排,导致返回错误的对象,所以需要volatile关键字声明
*/
obj = new SingleC();
}
}
}
return obj;
}
}
但是问题仍然无法解决,因为可以反射修改newFlag的值,来多次创建对象,非常无语
三、其他相关
关于枚举
c#的枚举,无法像Java一样实现线程安全、反射安全的单例,它真的只是枚举
饿汉式的优化
饿汉式的优点非常明显,利用了类加载的机制去实现,而缺点就是加载的太快了,类加载了,可能并不需要去加载对象,但是对象也被加载了,内存就被可能用不到的对象给浪费了一点。
利用静态内部类
public class SingleD
{
private SingleD()
{
}
private static class Inner
{
//外部类加载时,成员变量还没加载,非常完美
public static readonly SingleD obj=new SingleD();
}
public static SingleD GetInstance()
{
return Inner.obj;
}
}
最佳实践
真的有最佳实践?并没有,反射功能的强大,几乎让人防不胜防,所以我们需要进行一些内部的约定,不允许对单例类进行反射使用。那么这种情况下只需要考虑多线程的问题,那么最佳实践就是【饿汉式的优化】,它更简洁
更多推荐



所有评论(0)