扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
作者:中国IT实验室 来源:中国IT实验室 2007年8月26日
关键字:
我们看到的是一个简单的类图表,显示有一个 Singleton 对象的私有静态属性以及返回此相同属性的公共方法 Instance()。 这实际上是 Singleton 的核心。 还有其他一些属性和方法,用于说明在该类上允许执行的其他操作。 为了便于此次讨论,让我们将重点放在实例属性和方法上。
客户端仅通过实例方法来访问任何 Singleton 实例。 此处没有定义创建实例的方式。 我们还希望能够控制如何以及何时创建实例。 在 OO 开发中,通常可以在类的构造函数中最好地处理特殊对象的创建行为。 这种情况也不例外。 我们可以做的是,定义我们何时以及如何构造类实例,然后禁止任何客户端直接调用该构造函数。 这是在 Singleton 构造中始终使用的方法。 让我们看一下 Design Patterns 中的原始示例。 通常,将下面所示的 C++ Singleton 示例实现代码示例视为 Singleton 的默认实现。 本示例已移植到很多其他编程语言中,通常它在任何地方的形式与此几乎相同。
C++ Singleton 示例实现代码
// Declaration class Singleton { public: static Singleton* Instance(); protected: Singleton(); private: static Singleton* _instance; } // Implementation Singleton* Singleton::_instance = 0; Singleton* Singleton::Instance() { if (_instance == 0) { _instance = new Singleton; } return _instance; } |
让我们先花点时间分析一下此代码。 该简单类有一个成员变量,此变量是指向该类自身的指针。 注意,构造函数是受保护的,并且只有公共方法才是实例方法。 在实例方法实现中,有一个控制块 (if),它检查成员变量是否已初始化,如果没有的话,则创建一个新实例。 控制块中这种惰性初始化意味着仅在第一次调用 Instance() 方法时初始化或创建 Singleton 实例。 对于很多应用程序,这种方法效果很好。 但对于多线程应用程序,这种方法证明具有潜在危险的副作用。 如果两个线程同时进入控制块,则可能会创建该成员变量的两个实例。 要解决这一问题,您可能想只将重要部分放在控制块周围以确保线程安全。 如果您这样做,则将对实例方法的所有调用进行序列化处理,并且可能会对性能产生不利影响(取决于应用程序)。 正是由于这个原因,创建了此模式的另一个版本,它使用某种称为双重检验机制的功能。 下一个代码示例显示使用 Java 语法的双重检验锁定。
使用 Java 语法的双重检验锁定 Singleton 代码
// C++ port to Java class Singleton { public static Singleton Instance() { if (_instance == null) { synchronized (Class.forName("Singleton")) { if (_instance == null) { _instance = new Singleton(); } } } return _instance; } protected Singleton() { } private static Singleton _instance = null; } |
在使用 Java 语法的双重检验锁定 Singleton 代码示例中,我们直接将 C++ 代码移植到 Java 代码,以便利用 Java 关键部分块(已同步)。 主要差别是不再有单独的声明和实现部分,没有指针数据类型,并且采用了新的双重检验机制。 双重检验发生在第一个 IF 块上。 如果成员变量为空,则执行进入关键部分块,该块再次双重检验该成员变量。 仅在通过此最终测试后,才会实例化该成员变量。 一般来说,两个线程无法使用这种方法创建两个类实例。 另外,因为在第一次检查时没有出现线程阻塞,所以对此方法的大多数调用不会由于必须进入锁定而导致性能下降。 目前,在实现 Singleton 模式时,很多 Java 应用程序中都广泛使用这种方法。 这种方法很巧妙,但也有瑕疵。 某些优化编译器可以将惰性初始化代码优化掉或对其重新进行排序,并且会重新产生线程安全问题。 有关更深入的解释,请参阅 "The Double-Check Locking is Broken" Declaration。
另一种试图解决此问题的方法可能是,在成员变量声明中使用 volatile 关键字。 这应该告诉编译器不要对代码重新排序,并且放弃优化。 目前,这是唯一建议的 JVM 内存模型,并且不会立即解决该问题。
实现 Singleton 的最好方法是什么? 最终(而不是碰巧),Microsoft .NET 框架解决了所有这些问题,从而更易于实现 Singleton,却不会产生我们目前讨论的不利副作用。 .NET 框架以及 C# 语言允许我们在必要时通过替换语言关键字,将上述的 Java 语法移植到 C# 语法。 因此,Singleton 代码变为以下内容:
以 C# 编码的双重检验锁定
// Port to C# class Singleton { public static Singleton Instance() { if (_instance == null) { lock (typeof(Singleton)) { if (_instance == null) { _instance = new Singleton(); } } } return _instance; } protected Singleton() { } private static volatile Singleton _instance = null; } |
濠电姷鏁告慨鐑姐€傛禒瀣劦妞ゆ巻鍋撻柛鐔锋健閸┾偓妞ゆ巻鍋撶紓宥咃躬楠炲啫螣鐠囪尙绐為梺褰掑亰閸撴盯鎮惧ú顏呪拺闂傚牊鍗曢崼銉ョ柧婵犲﹤瀚崣蹇旂節婵犲倻澧涢柛瀣ㄥ妽閵囧嫰寮介妸褋鈧帡鏌熼挊澶婃殻闁哄瞼鍠栭幃婊堝煛閸屾稓褰嬮柣搴ゎ潐濞叉ê鐣濈粙璺ㄦ殾闁割偅娲栭悡娑㈡煕鐏炲墽鐭嬫繛鍫熸倐濮婄粯鎷呯粵瀣異闂佹悶鍔嬮崡鍐茬暦閵忋倕鍐€妞ゆ劑鍎卞皬闂備焦瀵х粙鎴犫偓姘煎弮瀹曚即宕卞Ο闀愮盎闂侀潧鐗嗛幊搴㈡叏椤掆偓閳规垿鍩ラ崱妞剧凹濠电姰鍨洪敋閾荤偞淇婇妶鍛櫤闁稿鍊圭换娑㈠幢濡纰嶉柣搴㈣壘椤︾敻寮诲鍫闂佸憡鎸鹃崰搴敋閿濆鏁嗗〒姘功閻绻涢幘鏉戠劰闁稿鎹囬弻锝呪槈濞嗘劕纾抽梺鍝勬湰缁嬫垿鍩為幋锕€宸濇い鏇炴噺閳诲﹦绱撻崒娆戝妽妞ゃ劌鎳橀幆宀勫磼閻愰潧绁﹂柟鍏肩暘閸斿矂鎮為崹顐犱簻闁圭儤鍨甸鈺呮倵濮橆剦妲归柕鍥у瀵粙濡歌閸c儳绱撴担绛嬪殭婵☆偅绻堝濠氭偄绾拌鲸鏅i悷婊冪Ч閹﹢鎳犻鍌滐紲闁哄鐗勯崝搴g不閻愮儤鐓涢悘鐐跺Г閸犳﹢鏌℃担鐟板鐎规洜鍠栭、姗€鎮╅搹顐ら拻闂傚倷娴囧畷鍨叏閹惰姤鈷旂€广儱顦崹鍌炴煢濡尨绱氶柨婵嗩槸缁€瀣亜閺嶃劎鈽夋繛鍫熺矒濮婅櫣娑甸崨顔俱€愬銈庡亝濞茬喖宕洪埀顒併亜閹哄棗浜鹃梺鎸庢穿婵″洤危閹版澘绫嶉柛顐g箘椤撴椽姊虹紒妯哄鐎殿噮鍓欒灃闁告侗鍠氶崢鎼佹⒑閸撴彃浜介柛瀣閹﹢鏁冮崒娑氬幈闁诲函缍嗛崑鍡樻櫠椤掑倻纾奸柛灞剧☉缁椦囨煙閻熸澘顏柟鐓庢贡閹叉挳宕熼棃娑欐珡闂傚倸鍊风粈渚€骞栭銈傚亾濮樺崬鍘寸€规洖缍婇弻鍡楊吋閸涱垽绱遍柣搴$畭閸庨亶藝娴兼潙纾跨€广儱顦伴悡鏇㈡煛閸ャ儱濡煎褜鍨伴湁闁绘ǹ绉鍫熺畳闂備焦瀵х换鍌毼涘Δ鍛厺闁哄洢鍨洪悡鍐喐濠婂牆绀堟慨妯挎硾閽冪喖鏌曟繛褍瀚烽崑銊╂⒑缂佹ê濮囨い鏇ㄥ弮閸┿垽寮撮姀鈥斥偓鐢告煥濠靛棗鈧懓鈻嶉崶銊d簻闊洦绋愰幉楣冩煛鐏炵偓绀嬬€规洟浜堕、姗€鎮㈡總澶夌处
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者