设计模式-单例模式

单例模式详解

前沿

《设计模式》一书总结了23个模式,依据各自的目的又被分为创建型模式(creational pattern)、结构型模式(structural pattern)和行为型模式(behavioral patterns),它们分别从对象的创建,对象和对象间的结构关系以及对象之间如何交互这三个方面入手,对面向对象系统建模方法给予了解释和指导。

  • 创建型模式描述怎样创建一个对象,以及如何隐藏对象创建的细节,从而使得程序代码不依赖于具体的对象,这样在增加一个新的对象时对代码的改动非常小。
  • 结构型模式描述类和对象之间如何进行有效的组织,形成良好的软件体系结构,主要的方法是使用继承关系来组织各个类。
  • 行为型模式描述类或对象之间如何交互以及如何分配职责,实际上它所牵涉的不仅仅是类或对象的设计模式,还有它们之间的通讯模式。

 

1.      单例模式

回答侧重:概念、类型、使用、注意

 

基本概念

单例对象(Singleton)是一种常用的设计模式,属于创建型模式,在《设计模式》一书中,作者这样来叙述单例模式的:确保一个类只有一个实例并提供一个对它的全局访问指针。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。

 

1.1.     单例模式的特点

      单例类只能有一个实例

      单例类必须自己创建自己的唯一实例(在自己类中调用new SingletonClass()

      单例类必须给所有其他对象提供这一实例(提供获取实例的接口)

 

1.2.     实现方法

单例模式的创建有以下几种方式:饿汉式单例,懒汉式单例 和单例注册表。

 

饿汉式单例:

       在这个类被加载时,静态变量instance会被初始化,此时该类的私有构造函数被调用,这时候,单例类的唯一实例就被创建出来了。(注:protected的构造方法可以被其子类以及在同一个包中的其它类调用

public class Singleton1{
       Private
static final
Singleton1 instance=new Singleton1();
      
       //
私有的默认构造函数

       Private Singleton1(){}
      
       //
静态工厂方法

       public
static
Singleton1 getInstance(){
              return instance;
       }
}

 

值得注意的是:由于构造函数是私有的,因此该类不能被继承如果你使用该实现,就无法改变它以便后来你可能想要允许多个单例类的实例Spring会根据配置,描述该类是单例还是原型模式)。

 

懒汉式单例:

         实例并没有直接实例化,而是在静态工厂方法被调用的时候才进行的,而且对静态工厂方法使用了同步化,以处理多线程并发的环境。

Public class Singleton2{
         Private static final Singleton2 instance=null;
        
         //
私有化构造方法来防止外部通过new 来创建该类的实例

         private Singleton1(){}
        
         //
使用 synchronzied 保证线程安全
         Public synchronized static Singleton2 getInstance(){
                  If(instance==null){
                           Instance=new Singleton2();
                  }
                  return instance;
         }
}

缺点:也是不能被继承

 

单例注册表

使用一个单例类注册表可以:

       在运行期指定单例类

       防止产生多个单例类子类的实例

      

       单例注册表模式克服了以上单例类不能被继承的缺点。带注册表单的单例类:

Public class RegSingleton{
         Static private HashMap registry=new HashMap();
         //
静态块,在类被加载时自动执行

         Static{
                  RegSingleton rs=new RegSingleton();
                  registry.put(rs.getClass().getName(),rs);
         }
         //
受保护的默认构造函数,如果为继承关系,则可以调用,克服了单例类不能为继承的缺点

         protected RegSingleton(){}
        
         //
静态工厂方法,返回此类的唯一实例

         public static RegSingleton getInstance(String name){
                  if(name==null){
                            name=” RegSingleton”;
                  }
                  if(registry.get(name)==null){
                           try{
                                    registry.put(name,Class.forName(name).newInstance());
                           }

                           catch(Exception ex){ex.printStackTrace();}
                  }
                  return (RegSingleton)registry.get(name);
         }
}

 

那实际中是怎么应用注册表方式的呢?

首先,封装注册表:

import java.util.HashMap;

import org.apache.log4j.Logger;

 

public class SingletonRegistry {

   public static SingletonRegistry REGISTRY = new SingletonRegistry();

 

   private static HashMap map = new HashMap();

   private static Logger logger = Logger.getRootLogger();

 

   protected SingletonRegistry() {

      // Exists to defeat instantiation

   }

   public static synchronized Object getInstance(String classname) {

      Object singleton = map.get(classname);

 

      if(singleton != null) {

         return singleton;

      }

      try {

         singleton = Class.forName(classname).newInstance();

         logger.info("created singleton: " + singleton);

      }

      catch(ClassNotFoundException cnf) {

         logger.fatal("Couldn't find class " + classname);    

      }

      catch(InstantiationException ie) {

         logger.fatal("Couldn't instantiate an object of type " + 

                       classname);    

      }

      catch(IllegalAccessException ia) {

         logger.fatal("Couldn't access class " + classname);    

      }

      map.put(classname, singleton);

      return singleton;

   }

}

 

注意我是把SingletonRegistry类作为一个单例模式实现的。我也通用化了这个注册表以便它能存储和取回任何类型的对象。例1显示了的Singleton类使用了这个注册表。 


1 使用了一个封装的注册表的Singleton 

import java.util.HashMap;

import org.apache.log4j.Logger;

 

public class Singleton {

 

   protected Singleton() {

      // Exists only to thwart instantiation.

   }

   public static Singleton getInstance() {

      return (Singleton)SingletonRegistry.REGISTRY.getInstance(‘xxx.Singleton’);

   }

}

 

1.3.     Spring的单例实现

当我们试图从Spring容器中取得某个类的实例时,默认情况下,Spring会才用单例模式进行创建。
<bean id="date" class="java.util.Date"/>
<bean id="date" class="java.util.Date" scope="singleton"/> (
仅为Spring2.0支持)
<bean id="date" class="java.util.Date" singleton="true"/>

以上三种创建对象的方式是完全相同的,容器都会向客户返回Date类的单例引用。那么如果我不想使用默认的单例模式,每次请求我都希望获得一个新的对象怎么办呢?很简单,将scope属性值设置为prototype(原型)就可以了

<bean id="date" class="java.util.Date" scope="prototype"/>

通过以上配置信息,Spring就会每次给客户端返回一个新的对象实例。

那么Spring对单例的底层实现,到底是饿汉式单例还是懒汉式单例呢?呵呵,都不是。Spring框架对单例的支持是采用单例注册表的方式进行实现的。不难发现,Spring中众多的单例类都是普通的JAVA BEAN类,并没有任何加工,唯一不同是使用了Spring配置来描述该类的形态(单例、原态),所以只能使用单例注册表来实现其单例模式,源码(部分)如下:

public abstract class AbstractBeanFactory implements ConfigurableBeanFactory{

 

    /**

     * 充当了Bean实例的缓存,实现方式和单例注册表相同

     */

    private final Map singletonCache=new HashMap();

   

    public Object getBean(String name)throws BeansException{

        return getBean(name,null,null);

    }

   

    ...

   

    public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{

        //对传入的Bean name稍做处理,防止传入的Bean name名有非法字符(或则做转码)

        String beanName=transformedBeanName(name);

        Object bean=null;

 

        //手工检测单例注册表

        Object sharedInstance=null;

   

        //使用了代码锁定同步块,原理和同步方法相似,但是这种写法效率更高

        synchronized(this.singletonCache){

            sharedInstance=this.singletonCache.get(beanName);

        }

   

        if(sharedInstance!=null){

            ...

            //返回合适的缓存Bean实例

            bean=getObjectForSharedInstance(name,sharedInstance);

   

        }else{

            ...

            //取得Bean的定义

            RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);

            ...

   

            //根据Bean定义判断,此判断依据通常来自于组件配置文件的单例属性开关  

            //<bean id="date" class="java.util.Date" scope="singleton"/>   

            //如果是单例,做如下处理  

            if(mergedBeanDefinition.isSingleton()){    

                synchronized(this.singletonCache){

                    //再次检测单例注册表

                    sharedInstance=this.singletonCache.get(beanName);

                    if(sharedInstance==null){

                        ...

                        try {

                            //真正创建Bean实例

                            sharedInstance=createBean(beanName,mergedBeanDefinition,args);

   

                            //向单例注册表注册Bean实例

                            addSingleton(beanName,sharedInstance);

                        }

                        catch (Exception ex) {...}

                        finally{...}

   

                    }

   

                }

   

                bean=getObjectForSharedInstance(name,sharedInstance);

   

            }

            //如果是非单例,即prototpye,每次都要新创建一个Bean实例

            //<bean id="date" class="java.util.Date" scope="prototype"/>

            else{

                bean=createBean(beanName,mergedBeanDefinition,args);

            }

        }

        ...

        return bean;

    }

}

 

 

转自:

http://xuganggogo.javaeye.com/blog/724511

http://xuganggogo.javaeye.com/blog/724511

 

<think>好的,我现在需要帮用户整理关于单链表操作实现的心得体会,包括算分析、改进方和经验教训。首先,我得回忆一下单链表的基本操作,比如入、删除查找等,以及它们的实现步骤。 用户提到了算分析,我需要分析这些操作的时间复杂度。比如,入和删除在已知节点的情况下是$O(1)$,但如果需要先查找,那就是$O(n)$。这点很重要,要明确区分不同情况下的性能差异。 接下来是改进设想。用户可能希望优化单链表的某些操作。比如,可以添加尾指针来提升尾部入的效率,这样就不用每次都遍历整个链表。另外,使用双向链表或者循环链表也是常见的改进方,但用户问的是单链表,可能需要说明这些扩展结构的不同应用场景。 经验教训部分,用户可能遇到了指针操作的问题,比如内存泄漏或者野指针。这时候需要强调正确管理指针的重要性,以及如何通过工具或测试来避免这些问题。比如,使用Valgrind检测内存泄漏,或者编写测试用例覆盖边界情况。 然后,我需要参考用户提供的引用内容。引用[1]提到链表操作和存储结构,这和用户的问题直接相关,应该引用到心得中。引用[3]涉及计算机系统知识,可能可以联系到内存管理或程序优化方面,但需要自然衔接,不能强行入。 生成相关问题的时候,要考虑用户可能进一步想知道的内容,比如单链表和双向链表的比较,或者如何测试链表代码的有效性。这些问题应该与用户的问题相关,并且帮助深化他们的理解。 最后,要确保整个回答结构清晰,分点列出,使用正确的LaTeX格式,并且引用标记自然。注意避免使用Markdown,保持口语化但专业,同时符合用户的中文要求。</think>### 单链表操作实现心得与算分析 在实现单链表操作时,核心在于理解其动态内存管理和指针操作的特性。以下是具体分析: #### 1. **算复杂度分析** - **入/删除操作**:在已知节点位置时,时间复杂度为$O(1)$(如头部入);若需遍历查找目标节点(如按值删除),则时间复杂度为$O(n)$[^3]。 - **查找操作**:必须从头节点开始遍历,时间复杂度为$O(n)$。 - **内存管理**:动态分配节点需注意内存泄漏问题,删除节点后需及时释放内存[^1]。 #### 2. **改进方** - **尾指针优化**:添加尾指针(`tail`)可提升尾部入效率,使尾操作从$O(n)$优化为$O(1)$。 - **虚拟头节点**:引入虚拟头节点(`dummyHead`)可简化边界条件处理(如空链表或头节点操作)。 - **缓存长度信息**:维护链表长度变量`size`,避免频繁遍历统计长度。 - **双向链表扩展**:若需要频繁反向操作,可改用双向链表(但需额外空间存储前驱指针)[^1]。 #### 3. **经验教训** - **指针操作陷阱**:未正确处理指针指向可能导致链表断裂(如删除节点时未更新前驱节点的`next`)。 - **内存泄漏**:动态分配的节点未释放会造成内存泄漏,可通过工具如Valgrind检测[^3]。 - **边界测试**:需覆盖空链表、单节点链表、头尾节点操作等场景,例如: ```c // 示例:删除头节点时的错误处理 if (head != NULL) { Node* temp = head; head = head->next; free(temp); } ``` - **代码可读性**:合理封装函数(如`createNode()`, `insertAfter()`),避免重复代码[^1]。 #### 4. **实际应用场景** - 适用于频繁入/删除且数据量动态变化的场景(如实现队列、LRU缓存淘汰算)。 - 链式存储结构在操作系统文件系统、内存管理中有广泛应用[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值