第7章 单例模式进阶

来源:互联网 发布:苹果手机打不开淘宝 编辑:程序博客网 时间:2024/06/04 18:39

源码地址:单例模式github地址

在c#实现单例模式

单例模式是最著名的模式在软件工程之一。 从本质上讲,一个单例类,只允许一个实例 创建,通常提供简单的访问该实例。 大多数情况下, 单例对象不允许任何参数被指定在创建实例时 另有第二个请求实例,但可以用不同的参数 是有问题的! (如果相同的实例应该访问的所有请求 相同的参数,工厂模式更合适)。 这篇文章只处理 的情况不需要参数。 通常是一个单例对象的要求 lazy——即创建实例创建,直到吗 第一需要。

有各种不同的方式在c#实现单例模式。 我将 现在他们在相反的顺序典雅,从最常见, 这不是线程安全的,和工作完全延迟加载的,线程安全的,简单吗 和高性能版本。 注意,在这里的代码,我省略了 私人 修改器,因为它是默认的类成员。 在许多其它语言(如Java、 是一个不同的默认, Private 应该使用。

然而,所有这些实现有四个共同特点:

一个构造函数,这是私人和无参数。 这可以防止其他类实例化它(这将是违反了模式)。 请注意,它还可以防止子类化——如果一个单例可以派生子类,它可以 从它派生出子类的两倍,如果每一个子类可以创建一个实例,该模式 违反了。 可以使用工厂模式如果你需要一个基本类型的一个实例, 但确切的类型不清楚到运行时。类是密封的。 这是不必要的,严格来说,由于上面的点, 但可以帮助JIT优化的东西更多。一个静态变量拥有单独创建的实例的引用,如果任何。一个公共静态的方法对单创建的实例的引用,创造 如果有必要。

还请注意,所有这些实现使用一个公共静态属性 实例 访问实例的方法。 在所有情况下,属性可以很容易地转换 一个方法,不影响线程安全或性能。

第一个版本,它不是线程安全的

// 最差的代码! 不要使用这种方式!public sealed class Singleton{    static Singleton instance=null;    Singleton()    {    }    public static Singleton Instance    {        get        {            if (instance==null)            {                instance = new Singleton();            }            return instance;        }    }}

上面不是线程安全的。 两个不同的线程可能两者兼而有之 有评估测试 如果(实例= = null) ,发现这是真的, 然后创建实例,这违背了单例模式。 请注意,事实上 创建了实例可能已经在计算表达式之前,但是 内存模型并不能保证新实例将被的价值 其他线程,除非通过了合适的记忆障碍。

第二个版本——简单的线程安全

public sealed class Singleton{    static Singleton instance=null;    static readonly object padlock = new object();    Singleton()    {    }    public static Singleton Instance    {        get        {            lock (padlock)            {                if (instance==null)                {                    instance = new Singleton();                }                return instance;            }        }    }}

这个实现是线程安全的。 线程锁在一个共享 对象,然后检查是否已经创建了实例之前创建的实例。 这负责记忆障碍的问题(如锁确保 所有读取发生逻辑锁后获得,并解锁确保所有发生写道 逻辑锁释放之前),确保一次只有一个线程将创建一个实例 (因为只有一个线程可以在一次的那部分代码,第二个线程 进入它,第一个线程将创建实例,所以表达式将评估为false)。 不幸的是,性能有一个锁获得每次请求的实例。

注意,而不是锁上 typeof(Singleton) 一些版本的 实现,我锁在一个静态变量的值是私人的类。 锁定对象,其他类可以访问和锁上(如类型)的风险 性能问题,甚至死锁。 这是我的一个总体风格偏好——无论 可能,只有锁对象专门创建的目的锁定,或 文档,他们将被锁定为特定目的(例如等待/脉冲队列)。 通常这样的对象应该是私有的类中使用。 这有助于使 编写线程安全的应用程序变得容易得多。

第三个版本——试图使用仔细检查锁定线程安全

// 这种写法不好是垃圾代码! 请不要使用这种方法!public sealed class Singleton{    static Singleton instance=null;    static readonly object padlock = new object();    Singleton()    {    }    public static Singleton Instance    {        get        {            if (instance==null)            {                lock (padlock)                {                    if (instance==null)                    {                        instance = new Singleton();                    }                }            }            return instance;        }    }}

第四个版本——不是lazy,但不使用锁的线程安全

public sealed class Singleton{    static readonly Singleton instance=new Singleton();    // 明确的告诉c#编译器静态构造函数    // 不像beforefieldinit标记类型    static Singleton()    {    }    Singleton()    {    }    public static Singleton Instance    {        get        {            return instance;        }    }}

正如你所看到的,这真的是非常简单,但为什么它是线程安全的和有多懒? 嗯,c#中的静态构造函数指定执行只有在类的一个实例 创建或引用一个静态成员,和每个AppDomain只执行一次。 考虑到 这张支票被新建的类型需要执行其他情况发生,它 将速度比添加额外的检查,如前面的例子。 有一些 然而,皱纹:

这不是其他实现一样lazy。 特别是,如果你有静态成员 除了 实例 这些成员将包括,第一个参考 创建一个实例。 这是纠正在未来实现。有并发症,如果其中一个静态构造函数调用了另一个调用 第一次了。 看的。 净规范(目前部分9.5.3分区(二) 有关的确切性质类型初始值设定项的更多细节,他们可能咬你, 但是值得意识到静态构造函数指的后果 在一个循环。类型初始值设定项的懒惰只是担保。 净的时候不是类型 标有一个特殊的标志 beforefieldinit 。 不幸的是, c#编译器(如提供的。 NET 1.1运行时,至少)是所有类型 没有一个静态构造函数(即一块看起来 像一个构造函数,但静态)作为标记 beforefieldinit 。   还要注意,它会影响性能,因为底部附近进行讨论 这篇文章的。

这个实现的一个快捷键你可以(并且只有这一个)就是 实例 一个公共静态只读的变量,完全摆脱房地产。 这使得基本框架代码绝对小! 然而,许多人宁愿有一个 财产,以防将来进一步的行动是必要的,和JIT内联可能 相同的性能。 (注意,静态构造函数本身仍然是必需的 如果你需要lazy。)

第五版——完全延迟实例化

public sealed class Singleton{    Singleton()    {    }    public static Singleton Instance    {        get        {            return Nested.instance;        }    }    class Nested    {        // Explicit static constructor to tell C# compiler        // not to mark type as beforefieldinit        static Nested()        {        }        internal static readonly Singleton instance = new Singleton();    }}

实例化是由第一个嵌套的静态成员的引用 类,它只发生在 实例 。 这意味着完全实现 懒惰,但以往所有的性能优势。 注意,尽管嵌套 类访问封闭类的私有成员,相反的是不正确的,因此 的必要性 实例 这里是内部。 不增加任何其他的问题, 不过,类本身是私人的。 代码是为了使更复杂 然而,懒实例化。

Performance vs laziness

实际上在很多情况下,你不需要完整的懒惰——除非你的类初始化 做一些特别费时,或有其他副作用,可能吗 可以省去上面所示的显式静态构造函数。 这可以提高性能 因为它允许JIT编译器来生成一个单一的检查(例如在一个方法) 以确保类型已经初始化,然后假定它从那时起。 如果你的 单例实例中引用一个相对紧密的循环,这可以使一个(相对) 显著的性能差异。 你应该决定是否完全延迟实例化 是必需的,和文档这一决定适当的内部类。 (见下文更多。)

异常

有时候,你需要做的工作在一个单例对象构造函数可能抛出异常,但是 整个应用程序可能不是致命的。 可能您的应用程序可能会 解决这个问题,想再试一次。 使用类型初始值设定项构造单例 在这个阶段就有问题。 不同运行时这种情况下的处理方式不同, 但我不知道任何的做想要的事情(再次运行初始化类型),和 即使一个人,在其他运行时代码将被打破。 为了避免这些问题,我想 建议使用第二种模式页面上列出,只是使用一个简单的锁,和经历 每次检查,建筑实例的方法/属性,如果没有已 成功建立。

性能

很多这个页面的原因源于人们想要聪明,从而来 双重检查锁定算法。 有一个锁定的态度是昂贵的 这是常见的和错误的。 我写一个非常快 基准 这只是十亿年一个循环的方式获取单例实例,尝试不同的变体。 这不是很科学,因为在现实生活中您可能想知道如果每个速度 迭代实际上涉及到一个方法的调用获取单例,等等。然而,它 显示一个重要点。 在我的笔记本电脑,最慢的解决方案(5倍)是锁定 (方案2)。是重要的? 可能不是,当你仍然设法记住它 获取单例 欧元 *在40秒。 这意味着,如果你是“仅” 获取单例四十万次每秒,收购的成本 是1%的性能,所以不会做很多改善它。现在,如果你 是 获取单例,通常不可能你使用它在一个循环吗? 如果你在意 对提高性能的一点,为什么不声明一个局部变量在循环外, 获取单例一次, 然后 循环。 宾果,甚至最慢的实现变得容易 足够了。

看到我会非常感兴趣 现实世界中 应用程序中使用的区别 简单的锁定和使用一个更快的解决方案实际上产生了显著的性能影响。

1 0
原创粉丝点击