zoukankan      html  css  js  c++  java
  • 不要用错单例模式

        SingleTon模式也许是被最广泛应用的模式,但是,最近看到的几个SingleTon不得不让我出一身冷汗。
        先来看看标准的反例:
    C#版
        public static SomeObject GetInstance()
        {
            
    if (_instance == null)
            {
                
    lock (_syncRoot)
                {
                    _instance 
    = new SomeObject();
                }
            }
            
    return _instance;
        }
    VB.Net版
        Public Shared Function GetInstance() As SomeObject
            
    If _instance Is Nothing Then
                
    SyncLock syncRoot
                    _instance 
    = New SomeObject()
                
    End SyncLock
            
    End If
            
    Return _instance
        
    End Function
        看到这段代码,相信大家都知道这个SingleTon的失败之处了吧,根本就是一个没有线程安全的SingleTon,用锁还用错地方了,汗水。。。假设创建SomeObject的时间比较长的话(例如访问数据库),有10个线程进来的话,创建出10个实例也是很有可能的,可以说是最失败的SingleTon。
        另外一种常见的错误是用双检锁的方式,这种错误的方式通常是因为JIT编译器优化而导致的,来看一下反例:
        public class BadSingleTon
        {
            
    private static readonly object _syncRoot = new object();
            
    private static BadSingleTon _instance;

            
    private BadSingleTon()
            {
                
    // label 5
            }

            
    public static BadSingleTon Instance
            {
                
    get
                {
                    
    // label 1
                    if (_instance == null)
                    {
                        
    // label 2
                        lock (_syncRoot)
                        {
                            
    // label 3
                            if (_instance == null)
                            {
                                
    // label 4
                                _instance = new BadSingleTon();
                            }
                        }
                    }
                    
    return _instance;
                }
            }
        }
        乍看上去,似乎很完美,当实例存在时,直接返回实例,不存在时去加锁,然后再判断是否存在实例(为什么?如果没有这个判断,就会和上面的例子一样,创建出多个实例),如果还是没有,那就创建实例。
        但是因为有编译器的优化,实际效果却有点不一样,假设现在有两个线程,分别是线程A和线程B,线程A先进入Instance属性,这时实例为空,线程A依次进入Label 1、2、3、4,然后创建对象,即Label 5,这时,如果对象还没有创建完成,但是因为编译器的优化,_instance已经不再是空了,这时,线程A被弹出,线程B进入,线程B发现实例不为空,直接返回实例,但是线程B并不知道,这个实例还没有创建完成,接下来线程B就是用这个没有完成创建的实例,进行各种操作,危险性不言而喻了吧。
        很多人喜欢用延迟创建SingleTon,但是,我个人觉得这种SingleTon需要对线程有一定的了解,而且,在基于.net的程序上,这个延迟似乎并不是非常重要,所以,我更倾向于最简单的方式,直接在静态字段上创建对象,这样可以轻松的规避锁和创建出多个实例,这是CLR级别保证的事情(如果CLR多次执行了,唯一的解释是,这时在多个AppDomain中执行的)。
        当然,这么做的缺点就是不是足够的延迟,但是,很多场合下,这个方式已经足够的延迟了。
        .net下什么时候会跑类型构造?可以肯定的回答,当第一次使用这个类型的时候。如果,这个单例类型中唯一对外公开的静态成员就是这个GetInstance方法或Instance属性,那么除了这个方法或属性被调用外,还有什么可以导致这个类型的构造被创建哪?
        所以,不需要太在意延迟创建,别忘了,写的代码越多,可能会出现的Bug也越多,既然在字段上直接创建没有什么太大的问题,为什么不这么用哪?
        当然,这里所说的SingleTon都是AppDomain级别的,如果要让多个AppDomain之间公用一个实例,那就不能这么简单的事情了。

  • 相关阅读:
    浅析Dagger2依赖注入实现过程
    浅谈Java的包装类
    多媒体编程基础之RGB和YUV
    多媒体编程基础之色彩空间
    Android Studio实用快捷键汇总
    Testin云测试平台初体验
    一张图看Goodle Clean设计架构
    一张图看Google MVP设计架构
    WampServer中MySQL中文乱码解决
    使用javamail发送包含八位验证码的邮件(完美解决中文乱码问题)
  • 原文地址:https://www.cnblogs.com/vwxyzh/p/781655.html
Copyright © 2011-2022 走看看