2018-06-13 孙小北

设计模式之单例模式(SingletonPattern)

养浩然之气,做博学之人

一、单例模式

单例指的是只能存在一个实例的类。在第一个使用者创建了这个类的实例之后,其后需要使用这个类的就只能使用之前创建的实例,无法再创建一个新的实例。一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。

分类:懒汉式单例、饿汉式单例及其改进。

优点:

 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。            

 2、避免对资源的多重占用。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

应用场景:

1. Windows的Task Manager(任务管理器)就是很典型的单例模式

2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。

4. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

5. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。

6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。

7. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。

9. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.

二、具体实现(Java)

通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。(注:Java反射机制是能够实例化构造方法为private的类的)

(1)懒汉式(线程不安全、不推荐)

懒汉式就是不在系统加载时就创建类的单例,而是在第一次使用实例的时候再创建

//懒汉式单例类.在第一次调用的时候实例化自己   
public class Singleton {  
    //定义私有构造器,表示只在类内部使用,即单例的实例只能在单例类内部创建
    private Singleton() {}  
    //定义一个私有类变量来存放单例,私有的目的是指外部无法直接获取这个变量,而要使用提供的公共方法来获取
    private static Singleton single=null; 
    //定义一个公共的公开的方法来返回该类的实例,由于是懒汉式,需要在第一次使用时生成实例
    public static Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
    }  
}

并发环境下很可能出现多个Singleton实例,要实现线程安全,可通过若下方式改进

改进1:线程安全(通过synchronized关键字实现线程安全)(不推荐)

    优点:第一次调用才初始化,避免内存浪费。
           缺点:加锁 synchronized 才能保证单例,但加锁会影响效率。每次都需要lock,是最耗时的。

//懒汉式单例类.在第一次调用的时候实例化自己   
public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null; 
    //为了线程安全,使用synchronized关键字来确保只会生成单例
    public static  synchronized Singleton getInstance() {  
         if (single == null) {    
             single = new Singleton();  
         }    
        return single;  
    }  
}

改进2:双重检查锁定(推荐)

这种方式采用双锁机制,安全且在多线程情况下能保持高性能。但是实现较复杂。

第一重判断,如果单例已经存在,那么就不再需要进行同步操作,而是直接返回这个实例,如果没有创建,才会进入同步块,同步块的目的与之前相同,目的是为了防止有两个调用同时进行时,导致生成多个实例,有了同步块,每次只能有一个线程调用能访问同步块内容,当第一个抢到锁的调用获取了实例之后,这个实例就会被创建,之后的所有调用都不会进入同步块,直接在第一重判断就返回了单例。第二重空判断的作用,当多个线程一起到达锁位置时,进行锁竞争,其中一个线程获取锁,如果是第一次进入则singleton为null,会进行单例对象的创建,完成后释放锁,其他线程获取锁后就会被空判断拦截,直接返回已创建的单例对象。

//懒汉式单例类.在第一次调用的时候实例化自己   
public class Singleton {  
    private Singleton() {}  
    private static Singleton single=null; 
    //为了线程安全,使用双重检查锁定
    public static Singleton getInstance() {  
        if (singleton == null) {    
            synchronized (Singleton.class) {    
               if (singleton == null) {    
                  singleton = new Singleton();   
               }    
            }    
        }    
        return singleton;   
    } 
}

(2)静态内部类。(推荐)

静态内部类方式既实现了线程安全,又避免了同步带来的性能影响。这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

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

这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程,它跟饿汉式方式不同的是:饿汉式方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比饿汉式方式就显得很合理。

(3)饿汉式(线程安全、推荐)

优点:没有加锁,执行效率会提高。
       缺点:类加载时就初始化,浪费内存。
       它基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
       return instance;  
    }  
}

一般情况下直接使用饿汉式就好,只有在要明确实现懒加载( lazy loading )效果时,才会使用内部类方式。如果涉及到反序列化创建对象时会试着使用枚举的方式来实现单例。如果有其他特殊的需求,可以考虑使用双重检查锁定。


参考书籍:《软件架构与模式》、《设计模式解析》、菜鸟教程之设计模式

编辑:孙小北

本文地址: https://www.xiaowangyun.com/wyblog/detail/?id=185

版权归属: www.xiaowangyun.com   转载时请以链接形式注明出处

0 条评论

快来评论

物以类聚

最新评论

2017-10-06

一辈子不长,只有珍惜了,才不至于后悔。

2017-10-06

懂得感恩,才能走得更远。

标签云

归档

取消

感谢您的支持,您的每一次打赏都是一次鼓励!

扫码支持
每一次支持,都是不懈的动力

打开支付宝扫一扫,即可进行扫码打赏哦