单例前端
在软件系统中,常常有这样一些特殊的类,必须保证它们在系统中只存在一个实例(eg:应对一些特殊状况,好比数据库链接池(内置了资源) 全局惟一号码生成器),才能确保它们的逻辑正确性、以及良好的效率。数据库
优势:单例的好处就是单例,就是全局惟一的一个实例
单例模式会阻止其余对象实例化其本身的单例对象的副本,从而确保全部对象都访问惟一实例安全
缺点:单例能够避免重复建立,可是也会常驻内存 除非是真的有必要,不然不要单例并发
使用单例对象(尤为在类库中定义的对象)时,开发人员必须记住本身不能使用new关键字实例化对象。由于可能没法访问库源代码,所以应用程序开发人员可能会意外发现本身没法直接实例化此类ide
如何绕过常规的构造器,提供一种机制来保证一个类只建立一个实例?函数
如何实现?将构造函数私有化,而后对外提供一个公开的静态方法,使用一个静态属性进行判断当前对象是否被建立测试
1 // 不要用这种方式 2 public class Singleton 3 { 4 private static Singleton _instance = null; 5 private Singleton() { } 6 public static Singleton CreateInstance() 7 { 8 if (_instance == null) 9 { 10 _instance = new Singleton(); 11 } 12 return _instance; 13 } 14 }
上面的方法是非线程安全的,多个不一样的线程能够同时进入这个方法,若是instance为空的而且这里返回真的状况下,均可以建立实例,这显然违反了单例模式,实际上,在测试之前,实例就已经有可能被建立了,可是内存模型不能保证这个实例能被其余的线程看到,除非合适的内存屏障已经被跨过了优化
咱们把上面的代码优化一下this
1 /// <summary> 2 /// 单例类:一个构造对象很耗时耗资源类型 3 /// 懒汉式单例模式 4 /// </summary> 5 public class Singleton 6 { 7 /// <summary> 8 /// 构造函数耗时耗资源 9 /// </summary> 10 private Singleton() 11 { 12 long lResult = 0; 13 for (int i = 0; i < 10000000; i++) 14 { 15 lResult += i; 16 } 17 Thread.Sleep(2000); 18 Console.WriteLine("{0}被构造一次", this.GetType().Name); 19 } 20 /// <summary> 21 /// 3 全局惟一静态 重用这个变量 22 /// </summary> 23 private static volatile Singleton _Singleton = null; 24 //volatile 促进线程安全 让线程按顺序操做 25 private static readonly object Singleton_Lock = new object(); 26 /// <summary> 27 /// 2 公开的静态方法提供对象实例 28 /// </summary> 29 /// <returns></returns> 30 public static Singleton CreateInstance() 31 { 32 if (_Singleton == null)//是_Singleton已经被初始化以后,就不要进入锁等待了 33 { 34 lock (Singleton_Lock) 35 //保证任意时刻只有一个线程进入lock范围 36 //也限制了并发,尤为是_Singleton已经被初始化以后 37 { 38 //Thread.Sleep(1000); 39 //Console.WriteLine("等待锁1s以后才继续。。。"); 40 if (_Singleton == null)//保证只实例化一次 41 { 42 _Singleton = new Singleton(); 43 } 44 } 45 } 46 return _Singleton; 47 } 48 49 //既然是单例,你们用的是同一个对象,用的是同一个方法,那还会并发吗 还有线程安全问题吗? 50 public int iTotal = 0; 51 public void Show() 52 { 53 //lock (Singleton_Lock) 54 //{ 55 this.iTotal++; 56 //} 57 } 58 59 public static void Test() 60 { 61 Console.WriteLine("Test1"); 62 Console.WriteLine(_Singleton.iTotal); 63 } 64 65 }
前端调用spa
1 { 2 List<Task> tasks = new List<Task>(); 3 for (int i = 0; i < 10000; i++) 4 { 5 tasks.Add(Task.Run(() => 6 { 7 Singleton singleton = Singleton.CreateInstance(); 8 singleton.Show(); 9 })); 10 } 11 Task.WaitAll(tasks.ToArray()); 12 Singleton.Test(); 13 //iTotal 是0 1 10000 仍是其余的 14 //其余值,1到10000范围内均可能 线程不安全 15 16 }
运行代码咱们会发现一个问题
iTotal 是0 1 10000 仍是其余的,
其余值,1到10000范围内均可能 线程不安全
为何呢?形成这种状况的缘由单例执行singleton.Show()方法时 iTotal在等于某个值时被附加屡次,由此获得结论:
即便是单例,变量也不是线程安全的,单例不是为了保证线程安全
如何优化?给show方法加把锁
1 public void Show() 2 { 3 lock (Singleton_Lock) 4 { 5 this.iTotal++; 6 } 7 }
单例还有另外的写法,以上是懒汉式单例模式,下面咱们来看看饿汉式
利用静态构造函数 程序第一次使用这个类型前被调用,且只调用一次
1 /// <summary> 2 /// 单例类:一个构造对象很耗时耗资源类型 3 /// 4 /// 饿汉式 5 /// </summary> 6 public class SingletonSecond 7 { 8 /// <summary> 9 /// 1 构造函数耗时耗资源 10 /// </summary> 11 private SingletonSecond() 12 { 13 long lResult = 0; 14 for (int i = 0; i < 10000000; i++) 15 { 16 lResult += i; 17 } 18 Thread.Sleep(1000); 19 Console.WriteLine("{0}被构造一次", this.GetType().Name); 20 } 21 /// <summary> 22 /// 静态构造函数:由CLR保证,程序第一次使用这个类型前被调用,且只调用一次 23 /// 24 /// 检测,初始化 25 /// 写日志功能的文件夹检测 26 /// XML配置文件 27 /// </summary> 28 static SingletonSecond() 29 { 30 _SingletonSecond = new SingletonSecond(); 31 Console.WriteLine("SingletonSecond 被启动"); 32 } 33 34 35 private static SingletonSecond _SingletonSecond = null; 36 public static SingletonSecond CreateInstance() 37 { 38 return _SingletonSecond; 39 }//饿汉式 只要使用类就会被构造 40 41 42 }
另一种相似的,利用静态字段建立对象
1 /// <summary> 2 /// 单例类:一个构造对象很耗时耗资源类型 3 /// 饿汉式 4 /// </summary> 5 public class SingletonThird 6 { 7 /// <summary> 8 /// 构造函数耗时耗资源 9 /// </summary> 10 private SingletonThird() 11 { 12 long lResult = 0; 13 for (int i = 0; i < 10000000; i++) 14 { 15 lResult += i; 16 } 17 Thread.Sleep(1000); 18 Console.WriteLine("{0}被构造一次", this.GetType().Name); 19 } 20 21 /// <summary> 22 /// 静态字段:在第一次使用这个类以前,由CLR保证,初始化且只初始化一次 23 /// 这个比今天构造函数还早 24 /// </summary> 25 private static SingletonThird _SingletonThird = new SingletonThird();//打印个日志 26 public static SingletonThird CreateInstance() 27 { 28 return _SingletonThird; 29 }//饿汉式 只要使用类就会被构造 30 31 32 33 34 public void Show() 35 { 36 Console.WriteLine("这里是{0}.Show", this.GetType().Name); 37 } 38 39 }