关于单例模式

来源:互联网 发布:网络扳手的意思 编辑:程序博客网 时间:2024/05/16 06:23

     最近不管是面试,还是笔试,单例模式都是一个比较常见的知识点,所以有必要去更深入地去了解一下。看了一些别人的文章,自己总结一下,也好加深记忆......

    

     注:并非原创,乃是看了其他一些大神的分析文章,把自己领悟到的部分摘录下。。。。。。    

 

     百度百科上的解释:单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

      单例模式不是一种技术,而是一种思想。

      单例模式的以下特点:
         1. 该类只有一个实例
         2. 该类自行创建该实例(在该类内部创建自身的实例对象)
         3. 向整个系统公开这个实例接口(提供一个全局的访问点)

 

应用场景

       对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。

 

在一个系统中,要求一个类有且仅有一个对象,如果出现多个对象就会出现“不良反应”时,则可以采用单例模式,具体的场景如下: 
     (1)要求生成唯一序列号的环境;  
     (2)在整个项目中需要有访问一个共享访问点或共享数据,例如一个Web页面上的计数器,可以不用每次刷新都记录到数据库中,使用单例模式保持计数器的值,

              并确保是线程安全的; 
     (3)创建一个对象需要消耗的资源过多,如要访问IO、访问数据库等资源; 
     (4)需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)

 

在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

 

-----------------------------------------------------------------------------------------------------------------------

单例模式的优点
       (1)由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的被创建、销毁,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显;
       (2)由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生   一个单例对象,然后永久驻留内存的方式来解决(在Java EE中采用单例模式时需要注意JVM垃圾回收机制);
      (3)单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
     (4)单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。
 

单例模式的缺点
     (1)单例模式没有接口,扩展很困难,若要扩展,除了修改代码没有第二种途径可以实现。单例模式为什么不能增加接口呢?因为接口对单例模式是没有任何的意义,它要    求 “自行实例化”,并且提供单一实例、接口或抽象类是不可能被实例化的。
     (2)单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。
     (3)单例模式与单一职责原则有冲突。一个类应该只实现一个的逻辑,而不关心它是否是单例的,决定它是不是要单例是环境决定的,单例模式把“要单例”和业务逻辑融合也在一个类中。

 

下面,还是用代码说话吧--------------------------------------------------------------------------------------

在java语言中,单例模式通常可以分成“饿汉式”和“懒汉式”。

 

饿汉式单例:

 

//饿汉式单例类.在类初始化时,已经自行实例化 public class Singleton {//已经自行实例化 private static final Singleton INSTANCE =  new Singleton();//私有的默认构造器private Singleton(){}//静态工厂方法public static Singleton getSingleton() {return INSTANCE;}}

 

优点: 

1.线程安全的

2.在类加载的同时已经创建好一个静态对象,调用时反应速度快。

缺点: 资源利用效率不高,可能getInstance永远不会执行到,但是执行了该类的其他静态方法或者加载了该类(class.forName),那么这个实例仍然初始化了
,而且,单例对象比较大的时候,会占较大的内存空间。

(饿汉式的创建方式在一些场景中将无法使用:譬如Kerrigan实例的创建是依赖参数或者配置文件的,在getInstance()之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

 

 

懒汉式单例:

       延迟加载/惰性加载

 

//懒汉式单例类.在第一次调用的时候实例化 public class Singleton {//注意,这里没有finalprivate static Singleton INSTANCE = null;//私有的默认构造器private Singleton(){}//静态工厂方法public static Singleton getSingleton() {if(INSTANCE == null) {INSTANCE = new Singleton();}return INSTANCE;}}


     分析:解决了资源利用率不高的问题,但是没有考虑多线程情况下的线程安全问题,那么我们可以在getSingleton方法加上synchronized关键字,就可以保证线程的安全了。

        但是这里有个很大(至少耗时比例上很大)的性能问题。除了第一次调用时是执行了Singleton的构造函数之外,以后的每一次调用都是直接返回instance对象。返回对象这个操作耗时是很小的,绝大部分的耗时都用在synchronized修饰符的同步准备上,因此从性能上说很不划算(不必要的同步开销同)。

 

那么,接下来

public class Singleton {private static Singleton INSTANCE = null;//私有的默认构造器private Singleton(){}//静态工厂方法public static Singleton getSingleton() {if(INSTANCE == null){synchronized(Singleton.class){if(INSTANCE == null) {INSTANCE = new Singleton();}}    }return INSTANCE;}}

     

     这种叫双重锁定检查(Double-Checked Locking)。

     除了第一次创建对象之外,其他的访问在第一个if中就返回了,因此不会走到同步块中。效率和线程安全的问题都得到了解决。

     这样就完美了吗?

     都这样问了,当然没有。

     由于Java编译器允许处理器乱序执行(out-of-order)(这就不详述了),偶尔会失败。

 

     所以,还有这么一种简洁有效的方法---使用内部类。


 

 public class Singleton {private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE= new Singleton();}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}}


          因为java机制规定(因为SingletonClass没有static的属性,因此并不会被初始化),内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了lazy),而且其加载过程是线程安全的(实现线程安全)。内部类加载的时候实例化一次instance。

        由于SingletonClassInstance是私有静态内部类,所以不会被其他类知道,同样,static语义也要求不会有多个实例存在。并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。

(要说缺点的话,就是第一次加载反应不快)

 

      至此,也就差不多了,“饿汉式”的实现是最简单的,也是最实用的,在单例对象不大,允许非懒加载的情况下使用!(一般用它就行!)

      用静态内部类实现的方法也是一种简单又高明的方法,推荐使用!!!

 

相关分析好文链接:

(http后加//)链接敏感

http:devbean.blog.51cto.com/448512/203501

http:www.iteye.com/topic/575052

http:blog.csdn.net/it_man/article/details/5787567
http:www.cnblogs.com/coffee/archive/2011/12/05/inside-java-singleton.html
http:www.cnblogs.com/whgw/archive/2011/10/05/2199535.html

      

------------------------------------------------------------------------------------一些不是很重要的内容

各种构造模式之间可以互相比较,但是没有优劣好坏之分,只有确定了上下文环境,才能谈应用什么模式。学习设计模式我觉得也没有必要去强背一些代码模版,应当去理解每种模式的出现的原因和解决的问题,当你发现你的设计需要更大灵活性时,设计便会向着合适的模式演化,这时候你就真正的掌握了设计模式。 

 

(单例模式是23个模式中比较简单的模式,应用也非常广泛,如在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理这些Bean的生命期,决定什么时候创建出来,什么时候销毁,销毁的时候要如何处理,等等。如果采用非单例模式(Prototype类型),则Bean初始化后的管理则交由J2EE容器,Spring容器不再跟踪管理Bean的生命周期。

     使用单例模式需要注意的一点就是JVM的垃圾回收机制,如果我们的一个单例模式在内存中长久不使用,JVM就认为这个对象是一个垃圾,在CPU资源空闲的情况下该对象会被清理掉,下次再调用时就需要重新产生一个对象。如果我们在应用中使用单例类作为有状态值(如计数器)的管理,则会出现回复原状的情况,应用就会出现故障。如果确实需要采用单例模式来记录有状态的值,有两种办法可以解决该问题:

  • 由容器管理单例的生命周期

     Java EE容器或者框架级容器,如Spring,可以让对象长久驻留内存。当然,自行通过管理对象的生命期也是一个可行的办法,既然有那么多的工具提供给我们,为什么不用呢?

  • 状态随时记录

     可以使用异步记录的方式,或者使用观察者模式,记录状态的变化,写入文件或写入数据库中,确保即使单例对象重新初始化也可以从资源环境获得销毁前的数据,避免应用数据丢失。

 

另还有一个登记式单例:

import java.util.HashMap;import java.util.Map;//登记式单例类.//类似Spring里面的方法,将类名注册,下次从里面直接获取。public class Singleton3 {    private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();    static{        Singleton3 single = new Singleton3();        map.put(single.getClass().getName(), single);    }    //保护的默认构造子    protected Singleton3(){}    //静态工厂方法,返还此类惟一的实例    public static Singleton3 getInstance(String name) {        if(name == null) {            name = Singleton3.class.getName();            System.out.println("name == null"+"--->name="+name);        }        if(map.get(name) == null) {            try {                map.put(name, (Singleton3) Class.forName(name).newInstance());            } catch (InstantiationException e) {                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            } catch (ClassNotFoundException e) {                e.printStackTrace();            }        }        return map.get(name);    }    //一个示意性的商业方法    public String about() {            return "Hello, I am RegSingleton.";        }        public static void main(String[] args) {        Singleton3 single3 = Singleton3.getInstance(null);        System.out.println(single3.about());    }}

 

暂且还看不