`

单例模式陷阱

阅读更多
         今天去jdon,看了它的设计研究栏目,bang有几篇评论单例模式的文章,声称“Singleton is evil”(见http://www.jdon.com/jive/article.jsp?forum=91&thread=17578),并且引用几篇外文页面佐证自己的观点,其中有一篇文章更是说,单例不仅不是一种模式,而是一种反模式。
        下面我谈谈我对单例模式的看法。逐一分析单例模式的陷阱,帮助大家正确使用单例模式。
(1) 陷阱一:调用函数的性能瓶颈
        在c++中,单例只有一种实现方式——LazySingleton, 实现如下(本文全部使用java代码):
  public   class  LazySingleton {
     private   static  LazySingleton m_instance = null ;
     private LazySingleton(){};
     synchronized public static LazySingleton getInstance(){
        if(m_instance==null)
            m_instance=new LazySingleton();
        return m_instance;
    }
} 

LazySingleton将对象的初始化推迟到调用的时候。并且为了防止多线程环境下产生多个实例,使用synchronized关键字保证函数getInstance调用的线程安全。synchronized关键字的存在保证了只会产生一个对象,但也成了多线程环境下的性能瓶颈。一个多线程的程序,到了这里却要排队等候成了一个单线程式的执行流程,这在高并发环境下是不可容忍的。而c++中可以使用双重检查机制将这种性能问题仅仅限制在第一次构造对象的时候,而java中不可以使用双重检查机制。
        但是java可以实现EagerSingleton,实现如下:

  public   class  EagerSingleton {
     private   static  EagerSingleton m_instance = new  EagerSingleton();
     private EagerSingleton(){};
     public static agerSingleton getInstance(){
        return m_instance;
    }

} 与LazySingleton相比,EagerSingleton将对象的初始化放到了类加载的时候。这样就避免了synchronized关键字的性能瓶颈。
(2)陷阱二:访问互斥共享资源
         EagerSingleton中访问互斥资源也要考虑线程安全问题。下面看一个例子:
public class EagerSingleton{
    private static EagerSingleton m_instance=new EagerSingleton();
    private HashMap map=new HashMap();
    private EagerSingleton(){};
    public static agerSingleton getInstance(){
        return m_instance;
    }
        public void refreshMap(Object key){
        synchronized(map){
            if(!map.contains(key))
                map.put(key,value);//value为此时的实时数据
        } 
    }

}因为该类是单例,可能多线程并发访问map,map非线程安全,需要加线程安全关键字,否则就掉入了访问互斥资源的陷阱。
(3)陷阱三:非法逻辑陷阱
        这种情况一般是滥用单例模式造成的,下面考虑一种滥用单例的情况。下面的代码的作用是getValueByName后,马上printValue即完成操作流程。
public class EagerSingleton{
    private static EagerSingleton m_instance=new EagerSingleton();
    private String value=null;
    private EagerSingleton(){};
    public static agerSingleton getInstance(){
        return m_instance;
    }
    synchronized public void getValueByName(String name){
        value=getByNameFromDateBase(name);
        
    }
    public viod printValue(){
        System.out.println(this.vaue);
    }
}
该类含有一私有属性value,在多线程环境下不能保证value值的合理逻辑,一线程getValueByName后,马上printValue,也有可能value的值已经被其他线程修改。这种情况就属于单例模式的滥用,该类根本不适合做成单例。
        消除非法逻辑的陷阱,可以通过将该类重构为纯粹的行为类完成。重构后的代码如下:

public class EagerSingleton{
    private static EagerSingleton m_instance=new EagerSingleton();
    private EagerSingleton(){};
    public static agerSingleton getInstance(){
        return m_instance;
    }
    private String getValueByName(String name){
        return getByNameFromDateBase(name);
        
    }
    public viod printName(String name){
        String value=getValueByName(String name);
        System.out.println(value);
    }
}
通过调用printName(String name)直接完成操作流程,将其中的私有属性处理成过程式的参数传递,将该类修改成纯粹的行为类。

        含有私有属性并且含有对它赋值操作的类并非都会调入该陷阱,构造函数里进行对私有属性赋值不会引起非法逻辑,如下代码

public class EagerSingleton{
    private static EagerSingleton m_instance=new EagerSingleton();
    private HashMap map==new HashMap();
    
    private EagerSingleton(){
        map.put(key,value);//value为此时的实时数据
    }
    public static agerSingleton getInstance(){
        return m_instance;
    }
}
构造函数里不必要加线程安全关键字也可以保证线程安全,因为类加载器是线程安全的,EagerSingleton只会在类加载的时候实例化一次,这样不会出现单例模式的线程不安全,也不会造成非法逻辑。
(4)陷阱四:单例陷阱的传递
        当含有对象作为单例类的私有属性时,陷阱不仅会出现在该类本身,还会传递到私有对象所在的类中。看如下代码:

public class EagerSingleton{
    private static EagerSingleton m_instance=new EagerSingleton();
    private NewClass newClass=nll;
    private EagerSingleton(){
        newClass=new NewClass();
    };
    public static agerSingleton getInstance(){
        return m_instance;
    }
    public viod printName(String name){
        String value=newClass.operationByNameAndReturnValue(String name);
        System.out.println(value);
    }

}乍一看,代码中除了构造函数对私有属性进行了初始化操作,其他地方没有对私有属性的赋值,不会引起非法逻辑陷阱。其实这个赋值操作可能隐含在newClass.operationByNameAndReturnValue(String name)操作,只有保证了NewClass的operationByNameAndReturnValue操作不会对它的私有属性赋值操作,才能保证真正的合理逻辑。同样,只有保证NewClass的operationByNameAndReturnValue操作没有掉入访问互斥资源陷阱,才能真正保证EagerSingleton没有掉入该陷阱。
        消除该陷阱的方法:(1)类方法的名称要合理,比如纯粹的行为方法名:interprete,excute,operation之类的方法中就不该含有对私有属性直接或者间接的赋值操作,每个方法的责任要明确。(2)单例类中尽量不要含有非单例类的实例作为私有属性(容器类除外),一定要有类的实例作为私有属性的时候,重新审视这个作为私有属性的类,是不是也应该设计成单例类;或者保证对它的初始化赋值限制在构造函数内。
分享到:
评论

相关推荐

    Android中单例模式的一些坑小结

    除了线程安全问题,还有其他与Android相关的单例模式陷阱。例如,如果单例持有Activity的引用,可能会导致内存泄漏。为了避免这种情况,单例应避免持有长时间存活的引用,或使用弱引用(WeakReference)来存储...

    细究单例那些你不知道的事(OC).zip

    单例模式是软件设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。在Objective-C(OC)编程中,...在实际开发中,应当根据项目需求选择合适的单例实现方式,并时刻警惕单例模式可能带来的设计陷阱。

    Head First 设计模式 +Java设计模式(第2版)

    这本书通过丰富的图像、故事和幽默的方式,引导读者了解23种经典的GoF(Gang of Four)设计模式,包括单例模式、工厂模式、观察者模式、装饰器模式、适配器模式、代理模式等。书中强调了如何在实际项目中选择和应用...

    c++ and Peris of Double Checked Locking

    描述:C++如何解决单例模式的线程安全问题 ### 关键知识点解析: #### 单例模式的线程安全挑战 单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。在多线程环境中,传统实现的单例模式可能...

    最新设计模式入门手册 chm

    创建型模式如工厂模式、抽象工厂模式、单例模式等,主要关注对象的创建过程,使得代码更加灵活,易于扩展。结构型模式如适配器模式、装饰器模式、代理模式等,关注如何组合不同的组件以构建更复杂的系统。行为型模式...

    C#高手进阶之陷阱和缺陷

    熟悉常用设计模式,如工厂模式、单例模式、观察者模式等,并结合实际场景选择合适的设计模式,能够提高代码的可读性和可维护性。 通过阅读"【京华志 www.jinghuazhi.com】C陷阱和缺陷.pdf",开发者可以深入了解这些...

    设计模式(讲的最详细版本)

    包括单例模式(Singleton)、工厂方法模式(Factory Method)、抽象工厂模式(Abstract Factory)、建造者模式(Builder)和原型模式(Prototype)。 2. **结构型模式**:这些模式涉及如何组合现有组件以构建更复杂...

    设计模式精解

    创建型模式处理对象的创建,如单例模式(Singleton)、工厂模式(Factory)和抽象工厂模式(Abstract Factory),它们关注如何优雅地创建对象,减少类间的耦合。结构型模式关注如何组合对象和类,比如适配器模式...

    深入探索c++对象模型 设计模式c++ pdf

    设计模式,如工厂模式、单例模式、观察者模式等,是软件设计中的通用解决方案。它们是经过时间和实践检验的,可以提高代码可读性、可维护性和可扩展性。将设计模式应用于C++编程,可以帮助开发者更好地组织代码,...

    Java设计模式国外珍藏版

    例如,单例模式确保一个类只有一个实例,并提供全局访问点,这在配置管理或者资源共享时非常有用。工厂模式则提供了一种创建对象的最佳方式,而无需指定具体类,增加了代码的灵活性和可扩展性。 衍生模式可能包括了...

    JavaScript设计模式+JavaScript模式+JavaScript异步编程

    - 单例模式:确保一个类只有一个实例,并提供全局访问点。 - 工厂模式:创建对象时避免使用new操作符,提供更灵活的实例化过程。 - 观察者模式:定义对象间的一种一对多依赖关系,当一个对象的状态发生改变时,...

    C++设计模式pdf高清版

    创建型模式关注对象的创建过程,如单例模式、工厂模式、建造者模式等;结构型模式关注如何组合现有组件构建更复杂的系统,如适配器模式、装饰器模式、代理模式等;行为型模式则关注对象间交互和职责分配,如观察者...

    java 设计模式教程

    创建型模式关注对象的创建,如单例模式(Singleton)、工厂模式(Factory)和抽象工厂模式(Abstract Factory)。这些模式帮助我们控制实例化过程,提供灵活的扩展和管理对象创建的方式。 结构型模式处理对象组合和...

    设计模式 java与模式

    2. **创建型模式**:包括单例模式、工厂模式(简单工厂、工厂方法、抽象工厂)、建造者模式和原型模式。这些模式主要用于对象的创建,使得代码结构更清晰,易于管理。 3. **结构型模式**:包括适配器模式、装饰器...

    设计模式-王翔 全本

    创建型模式关注对象的创建,如单例模式(Singleton)、工厂方法模式(Factory Method)和抽象工厂模式(Abstract Factory),它们旨在提供一种创建对象的最佳方式,同时保持系统的灵活性和扩展性。结构型模式涉及...

    常用设计模式及java程序

    例如,单例模式(Singleton Pattern)是一种确保一个类只有一个实例并提供一个全局访问点的设计模式。这在控制资源访问、降低系统复杂度等方面非常有用。 **2. 设计模式的应用** 设计模式帮助开发人员通过复用成功...

    java陷阱常见面试题

    7. 解释Java中的单例模式及其可能的陷阱。 8. 如何在Java中处理网络异常,防止资源泄露? 9. 请解释JVM的类加载过程及双亲委派模型。 10. 描述如何优化Java服务器的性能,包括JVM调优和数据库优化。 通过深入理解和...

    Java-single-model.rar_single

    单例模式是软件设计模式中的一种经典模式,它在Java编程中被广泛使用。这个模式的主要目的是确保一个类只有一个实例,并提供一个全局访问点。这样做的好处在于可以控制实例的数量,减少资源消耗,同时简化对共享资源...

    深入浅出设计模式(英文扫描版)

    常见的有工厂方法模式、抽象工厂模式、单例模式、建造者模式和原型模式。 2. **结构型模式**:关注于如何组合类或对象形成更大的结构。这包括适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式和代理...

Global site tag (gtag.js) - Google Analytics