`
vanadiumlin
  • 浏览: 504961 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

单例模式请不要滥用(转)

 
阅读更多

说到面向对象的设计模式,现在很多人都可以随便说出好几种常用的,但是有没有想过设计模式,即使是初学者也至少能说一下SingleTon和Factory Method这两个。那么,设计模式是不是随便怎么用都没问题哪?

这个问题从提问的方式上就可以看出,答案一定是否定的(大家也不是白白接受了这么多年的应试教育的)。 但是,就我个人的观察,滥用设计模式的绝对不是少数。而且越是简单的模式越会被滥用。

从最简单的模式——SingleTon开始。

说到SingleTon,我相信只要知道设计模式的,就知道SingleTon,也写过SingleTon,可谓是尽人皆知的设计模式了。就是这个尽人皆知的设计模式,却是被滥用的最厉害的设计模式,本篇就讨论一下关于SingleTon的滥用问题。

线程安全是个问题
首先GoF是站在一个纯OO的领域思考问题的,所以,很多其他领域的问题并没有考虑进来(事实上也不适合拉进来一起讲),然而实际编程者却不得不面对更多领域的问题(最常见的是并发领域)。这也就是为什么在GoF的SingleTon是如此的简单,而在Java或.net实际写SingleTon时,却需要注意锁的问题的根本原因。关于SingleTon是否是线程安全的,我倾向于把问题分解成两个部分:

SingleTon本身是否线程安全
SingleTon的实例是否线程安全
关于第一个,通过著名的Java下双检锁不安全问题,相信大家都已经十分清楚了,如果还有不清楚的,请查阅相关资料,本文就不再重复了。在这里,要重点说的是第二个问题,SingleTon的实例是否线程安全。

如果仔细看msdn的话,在大多数类上,msdn都写了这么一句话:Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

在msdn中文版中是:此类型的公共静态(在 Visual Basic 中为 Shared)成员是线程安全的。但不能保证任何实例成员是线程安全的。

在ms给出的类库,把静态成员都写成线程安全的,而对待大部分的实例成员却是放任其线程的不安全,而要求开发人员在开发时处理实例成员的线程不安全问题(我相信Java的类库也是类似的做法)。

那么在多线程程序中使用SingleTon,会引出什么问题哪?首先,SingleTon仅仅允许一个类只有一个实例。那么这里简单的做个推理:

在这个程序里面无论何时,当你需要这个类的实例的时候,都只能使用这个唯一的实例。
无论在这个程序的哪个线程中,当你需要这个类的实例的时候,都只能使用这个唯一的实例。
无论在这个程序中出现什么样的并发,当你需要这个类的实例的时候,都只能使用这个唯一的实例。
无论在这个程序中出现什么样的并发,当你需要调用这个类的实例的某个成员时,都只能使用这个唯一的实例的成员。
无论在这个程序中出现什么样的并发,为了保证程序是线程安全的,当你需要调用这个类的实例的某个成员时,都只能使用这个唯一的实例的成员,并且需要保证其线程安全。
无论在这个程序中出现什么样的并发,为了保证程序是线程安全的,当你需要调用这个类的实例的某个成员时,如果这个成员不是线程安全的,那么在使用这个唯一的实例的成员时都需要正确的线程同步。
发现问题了没有,被SingleTon的实例成员的线程安全性需要谁来保证?

选项A:SingleTon的实例保证所有的成员是线程安全的
选项B:SingleTon的实例不保证线程安全的,请大家在使用的时候都加上锁
如果选择A,那么,请检查那些被SingleTon的代码,看看有没有用到堆,检查对堆的任何调用是否都是线程安全的,或者已经同步的。

如果选择B,那么,请检查所有使用SingleTon的代码,看看是否都经过了可靠的线程同步(例如Lock那个SingleTon的对象),只要有一处不注意,就导致整个是线程不安全的(但是当下这么多写网页的人,有多少人会去关注那些资源是需要线程同步的吗?)。

有没有选项C?有,如果是类库的话,对外宣称程序是线程不安全的,请在使用前保证线程安全(例如COM中著名的STA);如果是应用程序的话就告诉客户,本程序是线程不安全的,出任何问题都是有可能的,当初合同就没说要保证线程安全(客户一定会抓狂)。

为什么要用SingleTon?
不知道大家有没有想过,当初为什么要把这个类型用SingleTon来做。我看到的大多数答案是节省资源和全局状态,固定算法的接口适配(不排除还有更好的答案)。

先讨论节省资源的问题,首先不new实例一定比new实例要节省资源(CPU和内存),这点不用质疑,但是如果要做到线程安全,似乎就要再考量一下了。

如果SingleTon实例本身的实现方式就保证了线程安全(仅使用堆栈和参数中的对象,对自身引用的对象只读),那么线程安全是0代价的。如果其实现方式涉及Lock等同步,那么冲突概率是多少,如果冲突概率足够的高,那么大多数时间线程将进入等待状态,导致大量占用时间资源和CPU资源。即使冲突概率很低,由于Lock需要同步Cache和内存,所以一样需要花费一些额外的代价(同步Cache的时间代价)。

说到这里,还觉得有Lock的SingleTon一定比new实例节省资源?未必吧,到底谁省资源还是要具体问题具体分析一下,不Profiling一下,谁又知道结果哪?

至于全局状态,可以的话,要尽量避免全局状态的使用,如果必须要使用的话,确实没什么好的方案,不过,建议作为全局状态使用的SingleTon需要保证所有成员的线程安全(否则,一个team member的小错误,就可能导致全局状态出错)。

第三种固定算法的接口适配,这是我比较提倡的SingleTon用法。这里涉及几个部分的要求:

算法固定
算法不存在状态,所有的变化均来自参数,且实现无需线程同步就可以保证线程安全
接收方要求符合某接口(泛指)
如果第一点不满足,那就不可能作为SingleTon存在。第二点是确保使用SingleTon的性能优势。而第三点是OO立场上的SingleTon必要性,否则为什么不用静态方法。

也许这3点比较抽象,举个实际点的例子:

view sourceprint?01 public class PersonNameComparer 

02     : IComparer<Person> 

03 { 

04     public int Compare(Person x, Person y) 

05     { 

06         if (x == y) 

07             return 0; 

08         if (x == null) 

09             return -1; 

10         if (y == null) 

11             return 1; 

12         return string.Compare(x.name, y.name); 

13     } 

14 }

第一,算法固定,就是比较2个人的名字,没有第二种算法,要是有那也是其它类的职责;第二,不存在状态,只用了x和y两个参数,没有线程同步;第三,因为需要在排序的场合需要IComparer<Person>接口的实例,因此不能使用静态方法(假设不能使用委托)。

因此,这个类型如果是SingleTon的话,那将是合理的(当然这里不用SingleTon也没问题)。

后话
N年前,我去面试某公司时,某面试官问我数据库联接能不能做SingleTon,我说不能,会引入很多问题,结果面试官很不满意。

N月前,面试某人,在谈到设计模式用在什么地方时候,举例说在Web项目中把一个WebService的代理类做成了SingleTon,结果我很不满意。哎,SingleTon啊SingleTon,真是GoF引入OO的最大的坑。
分享到:
评论

相关推荐

    C#版本的单例模式源码

    单例模式是软件设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。在C#中,实现单例模式有多种方法,这里我们将深入探讨这些方法及其优缺点。...因此,在实际项目中,应谨慎使用单例模式,避免滥用。

    设计模式-单例模式

    **设计模式——单例模式** 单例模式是一种广泛应用于软件设计中的创建型设计模式...总的来说,单例模式的应用旨在提高代码的可维护性和资源利用率,但也要注意避免滥用,以免造成设计复杂度增加和代码难以测试的问题。

    java单例模式在android中的应用

    Java的单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供全局访问点。在Android开发中,单例模式的应用尤其广泛,因为它能够有效管理资源,减少内存开销,提高性能。以下是对单例模式在Android中...

    IOS官方标准_单例模式

    单例模式是软件设计模式中的一种,它在iOS开发中被广泛应用,特别是在管理全局资源、配置或者需要跨类共享的对象时。单例模式的核心思想是确保一个类只有一个实例,并提供一个全局访问点,使得所有对象都可以通过这...

    单例模式 c++

    单例模式是软件设计模式中的一种,用于控制类的实例化过程,...总的来说,单例模式是一种常用的控制实例数量的设计模式,但在使用时应权衡其利弊,避免滥用。C++提供了多种实现方式,可以根据实际需求选择合适的方法。

    关于单例模式的知识要点

    单例模式是软件设计模式中的一种,其主要目的是控制类的实例化过程,确保一个类在整个应用程序中只有一个实例存在。这种模式在很多场景下都非常有用,例如管理共享资源、配置对象或者全局日志服务等。下面我们将深入...

    设计模式-单例模式(讲解及其实现代码)

    单例模式是软件设计模式中的一种,它保证一个类只有一个实例,并提供全局访问点。这种模式在许多场景下非常有用,比如控制共享资源、管理系统级别的对象,如数据库连接池或者线程池等。单例模式的核心在于限制类的...

    单例模式代码

    单例模式是软件设计模式中的一种,它保证一个类只有一个实例,并提供全局访问点。这种模式在需要频繁创建和销毁对象,或者对象创建代价较大,且需要全局共享资源时非常有用。下面我们将深入探讨单例模式的概念、实现...

    IOS单例模式Demo

    单例模式是软件设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。在iOS开发中,单例模式被广泛应用于管理共享资源、配置设置或者在整个应用程序生命周期内需要持续存在的对象,如网络请求管理者...

    设计模式之单例模式

    总的来说,单例模式是一种实用的设计模式,但在使用时应谨慎考虑其适用场景,避免滥用。在多线程和并发环境下,需特别注意线程安全问题。同时,随着编程语言和框架的发展,如Spring框架中的依赖注入,有时可以避免...

    设计模式——单例模式

    单例模式是软件设计模式中的一种经典模式,它在许多实际场景中被广泛使用,尤其在需要控制类的实例只有一个的情况下。单例模式确保无论在程序的任何地方,只要通过特定方式请求该类的实例,始终只会返回同一个对象。...

    单例模式七种写法_转

    此外,单例模式虽然简单,但其滥用可能会带来一系列的问题。例如,单例可能导致全局状态,使得代码难以测试和维护。在面向对象设计中,应当遵循“单一职责原则”,尽量避免不必要的全局状态,以提升系统的可扩展性...

    设计模式 For iOS-02-单例模式

    单例模式是一种常用的软件设计模式,特别是在iOS开发中,它确保一个类仅有一个实例,并提供一...在实际开发中,我们应当根据具体的应用需求和环境选择合适的实现方式,并且在设计时权衡单例模式带来的利弊,避免滥用。

    设计模式之单例模式详解.pdf

    设计模式之单例模式详解 单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。 单例模式的实现主要是...

    深入浅出单例Singleton模式

    【深入浅出单例Singleton模式】 ...总的来说,单例模式是一种强大的设计模式,但需要谨慎使用,避免滥用导致设计复杂性增加和代码难以维护。理解其背后的原理和潜在问题,才能在适当的情境下有效地利用它。

    Singleton 单例模式的介绍以及解析

    单例模式是软件设计模式中的一种经典模式,它在Java、C++、Python等众多编程语言中被广泛应用...总的来说,理解并正确运用单例模式,可以有效提高代码的复用性和可维护性,但也要注意避免滥用,以免带来不必要的问题。

    第五讲单例模式

    单例模式是设计模式中的重要概念,它在软件开发中被广泛应用,特别是在需要全局唯一实例的场景下。在本讲“第五讲单例模式”中,我们将深入探讨这个模式的原理、实现方式以及其在实际编程中的应用。 单例模式的核心...

    com_枚举方式实现单例模式_代码详解.rar

    在软件设计模式中,单例模式是一种常用的创建型模式,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。...记住,在使用单例模式时,应确保其确实符合系统需求,避免滥用导致设计复杂度增加。

    设计模式ForiOS-02-单例模式.pdf

    单例模式是一种软件设计模式,它的核心思想是确保一个类在整个应用程序中只有一个实例,并提供一个全局访问点来获取这个实例。这种模式在iOS开发中非常常见,因为许多系统类如UIApplication、NSFileManager等都采用...

Global site tag (gtag.js) - Google Analytics