单例

在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例(eg:应对一些特殊情况,比如数据库连接池(内置了资源)  全局唯一号码生成器),才能确保它们的逻辑正确性、以及良好的效率。

优点:单例的好处就是单例,就是全局唯一的一个实例
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例

缺点:单例可以避免重复创建,但是也会常驻内存 除非是真的有必要,否则不要单例

使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类

如何绕过常规的构造器,提供一种机制来保证一个类只创建一个实例?

如何实现?将构造函数私有化,然后对外提供一个公开的静态方法,使用一个静态属性进行判断当前对象是否被创建

 // 不要用这种方式
public class Singleton
{
    private static Singleton _instance = null;
    private Singleton() { }
    public static Singleton CreateInstance()
    {
        if (_instance == null)
        {
            _instance = new Singleton();
        }
        return _instance;
    }
}

上面的方法是非线程安全的,多个不同的线程可以同时进入这个方法,如果instance为空的并且这里返回真的情况下,都可以创建实例,这显然违反了单例模式,实际上,在测试以前,实例就已经有可能被创建了,但是内存模型不能保证这个实例能被其他的线程看到,除非合适的内存屏障已经被跨过了

我们把上面的代码优化一下

     /// <summary>
/// 单例类:一个构造对象很耗时耗资源类型
/// 懒汉式单例模式
/// </summary>
public class Singleton
{
/// <summary>
/// 构造函数耗时耗资源
/// </summary>
private Singleton()
{
long lResult = ;
for (int i = ; i < ; i++)
{
lResult += i;
}
Thread.Sleep();
Console.WriteLine("{0}被构造一次", this.GetType().Name);
}
/// <summary>
/// 3 全局唯一静态 重用这个变量
/// </summary>
private static volatile Singleton _Singleton = null;
//volatile 促进线程安全 让线程按顺序操作
private static readonly object Singleton_Lock = new object();
/// <summary>
/// 2 公开的静态方法提供对象实例
/// </summary>
/// <returns></returns>
public static Singleton CreateInstance()
{
if (_Singleton == null)//是_Singleton已经被初始化之后,就不要进入锁等待了
{
lock (Singleton_Lock)
//保证任意时刻只有一个线程进入lock范围
//也限制了并发,尤其是_Singleton已经被初始化之后
{
//Thread.Sleep(1000);
//Console.WriteLine("等待锁1s之后才继续。。。");
if (_Singleton == null)//保证只实例化一次
{
_Singleton = new Singleton();
}
}
}
return _Singleton;
} //既然是单例,大家用的是同一个对象,用的是同一个方法,那还会并发吗 还有线程安全问题吗?
public int iTotal = ;
public void Show()
{
//lock (Singleton_Lock)
//{
this.iTotal++;
//}
} public static void Test()
{
Console.WriteLine("Test1");
Console.WriteLine(_Singleton.iTotal);
} }

前端调用

                {
List<Task> tasks = new List<Task>();
for (int i = ; i < ; i++)
{
tasks.Add(Task.Run(() =>
{
Singleton singleton = Singleton.CreateInstance();
singleton.Show();
}));
}
Task.WaitAll(tasks.ToArray());
Singleton.Test();
//iTotal 是0 1 10000 还是其他的
//其他值,1到10000范围内都可能 线程不安全 }

运行代码我们会发现一个问题

iTotal 是0  1   10000  还是其他的,
其他值,1到10000范围内都可能   线程不安全
为什么呢?造成这种情况的原因单例执行singleton.Show()方法时 iTotal在等于某个值时被附加多次,由此得到结论:
即使是单例,变量也不是线程安全的,单例不是为了保证线程安全
如何优化?给show方法加把锁
         public void Show()
{
lock (Singleton_Lock)
{
this.iTotal++;
}
}

单例还有另外的写法,以上是懒汉式单例模式,下面我们来看看饿汉式

利用静态构造函数 程序第一次使用这个类型前被调用,且只调用一次

     /// <summary>
/// 单例类:一个构造对象很耗时耗资源类型
///
/// 饿汉式
/// </summary>
public class SingletonSecond
{
/// <summary>
/// 1 构造函数耗时耗资源
/// </summary>
private SingletonSecond()
{
long lResult = ;
for (int i = ; i < ; i++)
{
lResult += i;
}
Thread.Sleep();
Console.WriteLine("{0}被构造一次", this.GetType().Name);
}
/// <summary>
/// 静态构造函数:由CLR保证,程序第一次使用这个类型前被调用,且只调用一次
///
/// 检测,初始化
/// 写日志功能的文件夹检测
/// XML配置文件
/// </summary>
static SingletonSecond()
{
_SingletonSecond = new SingletonSecond();
Console.WriteLine("SingletonSecond 被启动");
} private static SingletonSecond _SingletonSecond = null;
public static SingletonSecond CreateInstance()
{
return _SingletonSecond;
}//饿汉式 只要使用类就会被构造 }

另外一种类似的,利用静态字段创建对象

     /// <summary>
/// 单例类:一个构造对象很耗时耗资源类型
/// 饿汉式
/// </summary>
public class SingletonThird
{
/// <summary>
/// 构造函数耗时耗资源
/// </summary>
private SingletonThird()
{
long lResult = ;
for (int i = ; i < ; i++)
{
lResult += i;
}
Thread.Sleep();
Console.WriteLine("{0}被构造一次", this.GetType().Name);
} /// <summary>
/// 静态字段:在第一次使用这个类之前,由CLR保证,初始化且只初始化一次
/// 这个比今天构造函数还早
/// </summary>
private static SingletonThird _SingletonThird = new SingletonThird();//打印个日志
public static SingletonThird CreateInstance()
{
return _SingletonThird;
}//饿汉式 只要使用类就会被构造 public void Show()
{
Console.WriteLine("这里是{0}.Show", this.GetType().Name);
} }

本文参考文档:https://csharpindepth.com/Articles/Singleton#unsafe

最新文章

  1. Boost条件变量condition_variable_any
  2. malloc分配的内存空间是连续的吗
  3. 内部类访问外部类的变量必须是final吗,java静态方法中不能引用非静态变量,静态方法中不能创建内部类的实例
  4. 解读Unity中的CG编写Shader系列五(理论知识)
  5. php 用户登录验证
  6. 现代程序设计——homework-08
  7. Aizu 2302 On or Off dfs/贪心
  8. 李洪强iOS开发之-环信04_消息
  9. webform 不实用office控件导出excel StringBuilder 类型拼接字符串表格导出excel
  10. 01-编写CMS注意事项
  11. 关于ajax post请求,参数过大产生的问题解决 Java
  12. XCode Build Settings中几种Search Paths
  13. RocketMq发送消息出现com.alibaba.rocketmq.client.exception.MQBrokerException: CODE: 2 DESC: [TIMEOUT_CLEAN_QUEUE]broker busy, start flow control for a while, period in queue: 201ms, size of queue: 1
  14. VSCode的Python扩展下程序运行的几种方式与环境变量管理
  15. mysql千万级数据量查询出所有重复的记录
  16. 通过使用Web Workers,Web应用程序可以在独立于主线程的后台线程中,运行一个脚本操作。这样做的好处是可以在独立线程中执行费时的处理任务,从而允许主线程(通常是UI线程)不会因此被阻塞/放慢。
  17. orcl数据库锁等级研究小记
  18. 【深入spring】IoC容器的实现
  19. [JS] ECMAScript 6 - String, Number, Function : compare with c#
  20. 全排列+字符串查找|扑克排序|2014年蓝桥杯A组题解析第六题-fishers

热门文章

  1. 从入门到实践:创作一个自己的 Helm Chart
  2. HDU 1847
  3. Docker资源管理
  4. 2019nc#3
  5. hdu-6621 K-th Closest Distance
  6. 牛客小白月赛6 J 洋灰三角 数学
  7. CodeForces 311 B Cats Transport 斜率优化DP
  8. [HNOI2002]沙漠寻宝 题解
  9. 题解 UVA11000 【Bee】
  10. SpringCloud Feign 之 Fallback初体验