对Singleton的实现方法做一个总结

原帖:

http://www.cnblogs.com/ASPNET2008/archive/2008/05/09/1190328.html

首先直接文章作者及回复中的兄弟提到的两种Singleton方法。

第一种

    public class Singleton
    
{
        
private static Singleton _instance = null;
        
private static readonly object lockHelper = new object();
        
private Singleton()
        
{
        }

        
public static Singleton CreateInstance()
        
{
            
//这样lock以及lock块内的代码只会在第一次调用CreateInstance方法的时候执行,
            
//第一次调用该方法后_instance就不再为null了,if块内的代码就无须执行了
            if (_instance == null)
            
{
                
lock (lockHelper)
                
{
                    
if (_instance == null)
                        _instance 
= new Singleton();
                }

            }

            
return _instance;
        }

    }

第二种

public class Singleton
    
{
        
private static readonly Singleton _instance = new Singleton();

        
static Singleton() { }

        
private Singleton() { }

        
public static Singleton CreateInstance() return _instance; }
    }

先说说以上两种方法。 

第一种方法, 显而易见的, 性能不佳; 而且最好加上一句Thread.MemoryBarrier()。 这是为了线性化内存存取, 避免处理器调整指令顺序导致问题, 至于到底怎么个坏掉法, 我就糊里糊涂了。 但至少从这个角度看, 其实第一种方法, 虽然我们最常用, 如果不加这么一句, 可能反而是不安全的。

 第二种方法, 有兄弟说它不是线程安全的, 事实上它是线程安全的, 但不一定是AppDomain安全的。 在同一个AppDomain内, 初始化仅进行一次。但是这种方法有一个细节。 对于没有静态构造函数的类, .NET会加上一个BeforeFieldInit的标志,它的MSDN解释是:Specifies that calling static methods of the type does not force the system to initialize the type.  这就是说, 假设Singleton存在一个静态方法, 比如Singleton.Do(), 这个Do被执行了, 但是_instance并不一定被初始化(当然也不一定不被),只要没有其它条件触发(在后面我们会想一个办法, 保证一定得到这个LazyLoad的效果); 也就是说, 初始化的时间是不固定的; 而且还有别的可能:  比如在程序集被加载, 在该类被某种方式访问, 甚至一个很随机的时刻。 传说中初始化仅仅保证在该字段被访问之前, 由运行时决定, 到底咋回事就不是我关心的范畴了。

其实早晚本来也是无所谓的, 关键是注意到这一点, 可以避免并没有触发初始化, 可是流程上人脑中有一个预期的行为,进行编程时所造成的不确定性(尽管很难想象能够掉进这个陷阱的场景,估计得特别特殊的应用, 比如你脑海里以为初始化了, 就向其它东西发一个信号, 而且会造成问题等等)。

如果存在静态构造函数, 那么当这个类的任意成员被访问前, 会进行初始化。 微软这么设计显然是有道理的: 既然写了静态构造器, 那么编程者很显然希望它最早执行, 同时也就会捎带上初始化。 但这种方式会导致, 如果咱们访问Singleton.Do(), 那么所有的字段都会被初始化。也许我们调用Do()的时候因为用不着这个实例, 而且这个实例会耗费很多资源,于是就想尽量Lazy Load用不着就不new, 这样要么使用第一种方法, 或者引出了第三个方法。

    public class Singleton
    
{
        
private Singleton() { }

        
public static Singleton CreateInstance() return Holder._instance; }

        
private class Holder
        
{
            
static Holder()
            
{
            }


            
public static readonly Singleton _instance = new Singleton();
        }

    }

用内部私有的类Holder玩一个花招, 实际上这是一种最好的办法: 当Holder被访问时, new Singleton(), 而Do()的时候, 则不会这么做。这样, 它既有第一种方法LazyLoad的特性, 又具有第二种方法的直接性和性能, 还避免了乱七八糟的很难搞明白的问题。

回头再说说关于第一种方法的另一个替代品, 这样:

public class Singleton
{
            
public static Singleton _instance = null;

            
private Singleton()
            
{

            }


            
public static Singleton CreateInstance()
            
{
                Interlocked.CompareExchange
<Singleton>(ref _instance, new Singleton(), null);
                
return _instance;
            }

}


因为Interlocked.CompareExchange是一个原子操作, 所以是线程安全的。不过注意那个new Singleton(), 它会不断的被执行。 如果构造的过程很耗费资源, 这是一个问题。 其实利用delegate, 我们可以有一些新玩法, 把包括第二次及以后进入CreateInstance的开销降到最低; 如果谁想到了可以给我留言。(Update: 我的做法放在了第20楼, 注意脑袋回复中提到的问题, 其实这种做法我一般用在别处完成更贴切的任务, 具体分析日后再说了)

最后, 稍加改动, 可以把Singleton成泛型的。不过个人感觉意义不大了, 除非用Singleton的地方太多, 懒得打代码, 也可以考虑。 嗯, 结束了么? 不知道大家觉得这些是不是也一塌糊涂,自己感觉我不太适合写这类文章....

posted on 2008-05-10 02:02  怪怪  阅读(6535)  评论(31编辑  收藏  举报

导航