设计模式学习01―单例模式(一)

2014-11-23 21:39:00 · 作者: · 浏览: 25
一、动机与定义
系统中有些资源只能有一个,或者一个就够,多个浪费。例如一个系统只能有一个窗口管理器或文件系统、一个系统只能有一个计时器或序号生成器、web系统只能有一个页面计数器等等。此时,最好就需要把这些资源设置成有且仅有一个实例。 代码中也就是如何保证一个类只有一个实例并且这个实例能够被访问呢?只有一个实例的就意味着不能让其他类来实例化,也就是只能自己实例化自己。能够被访问也就意味着自身要对外提供全局方法来获取到这个实例,这就是单例模式。 单例模式定义:确保某一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例。 单例模式通常代表着系统具有唯一性的资源。主要有3点:只有一个实例;自行创建这个实例;自行向整个系统提供这个实例。
二、结构与类图
单例模式是创建型模式,其实结构非常简单,需要注意以下3点: 1、构造方法私有:不让外部实例化,只能将构造函数私有; 2、提供一个公共静态方法获取实例:获取这个实例前是没有实例的,只能用静态的。 3、实例保存到自身静态私有属性上:获取方法是静态的,实例当然也只能是静态的,最好是final的,单例不允许修改; 通用类图如下: \ \ 代码如下:
public class Singleton {

    private static final Singleton instance = new Singleton();

    private Singleton() {

    }

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

三、适用场景及效果(优缺点)
1、只需要1个实例,多了浪费,主要用于节约系统资源,创建一个对象需要消耗过多资源时,考虑将这个对象缓存,设计成单例的,如创建某些程序启动配置对象读取、操作系统的文件系统等,只需要创建一个就够了,多了浪费; 2、只需要1个实例,多了出错,如计数器,唯一序列号生成器等; 3、单例意味着多线程使用(如果单线程使用,单例完全没有意义了),多线程下可以控制单一共享资源的访问和线程间通讯,避免对同一资源的多重占用,如仅有1个打印机,各个线程自行调用会对一个资源多重占用,单例模式可以统一管理对打印机的访问,还有如数据库连接池、线程池、日志应用等。 4、大量无状态的类实例,如需要大量静态常量或方法(有时也可以定义成static)可以考虑使用单例模式,如web开发中的service层,都是业务无状态的逻辑处理类,还有工具类和方法等,都可以设计成单例模式,这也是Spring框架中配置的bean默认都是单例的。 优点(使用后的效果): 1、单例只有一个实例,也只创建一次,可以节约系统资源,特别当这个对象需要频繁地创建和销毁时,而且创建和销毁要比较多的资源时; 2、能避免对单一资源的多重占用,进行统一管理。 3、单例模式可以在系统设置全局访问点,优化和共享资源访问。 缺点: 1、没有接口,扩展困难,无法适应变化,基本上只能修改 源码。(为什么没接口,就一个实例,接口没意义); 2、测试麻烦,单例没完成,无法测试; 3、与单一职责冲突。 单例模式可以分为有状态的和无状态的,无状态的单例对象不可变的,一般就是提供一些工具方法,有状态的单例对象是可变的,常用来给系统当作状态库,提供一些状态,如序列号生成器等。
四、示例
比如要做一个页面计数器,可以使用单例模式,非常简单,直接看代码
//页面计数器
public class PageCounter {

    private static final PageCounter instance = new PageCounter();
    // 计数器
    private AtomicLong counter = new AtomicLong(0);

    private PageCounter() {

    }

    public static PageCounter getInstance() {
        return instance ;
    }

    public void add() {
        counter.getAndAdd(1);
    }

    public long get() {
        return counter.get();
    }

}

五、模式扩展
说到单例模式,很多人想到的是如何创建单例模式,有很多种创建方法,懒汉、恶汉、双重锁等等,此处大概介绍一下。 第一种(饿汉)
//饿汉模式(推荐),类加载时就创建了
//优点:1、线程安全;2、调用getInstance时速度快
//缺点:1、无法延迟加载;2、有可能浪费资源,无人调用getInstance()时,仍然创建了实例
public class Singleton01 {

    private static final Singleton01 instance = new Singleton01();

    private Singleton01() {
    }

    public static Singleton01 getInstance() {
        return instance ;
    }
}
第二种(饿汉变种)
//饿汉模式变种,类加载时就创建了,和上一个模式区别不大,只是能在static中加入逻辑处理
public class Singleton02 {

    private static Singleton02 instance = null;

    static {
        // 此处可以写一些逻辑
        instance = new Singleton02();
    }

    private Singleton02() {
    }

    public static Singleton02 getInstance() {
        return instance ;
    }
}
第三种(懒汉)
//懒汉(线程不安全),用到了再去初始化
//优点:延迟加载
//缺点:致命的并发问题,可能导致创建多次
public class Singleton03 {

    private static Singleton03 instance = null;

    private Singleton03() {
    }

    public static Singleton03 getInstance() {
        if ( instance == null ) {
            // 此处有并发问题
            instance = new Singleton03();
        }
        return instance ;
    }
}
第四种(懒汉变种)
//懒汉(线程安全)
//优点:延迟加载
//缺点:效率低下,初始化完毕后,getInstance()方法根本不需要同步了
public class Singleton04 {

    private static Singleton04 instance = null;

    private Singleton04() {
    }

    public synchronized static Singleton04 getInstance() {
        if ( instance == null ) {
            instance = new Singleton04();
        }
        return instance ;
    }
}
第五种(双重锁定检查)
//双重锁定检查
//优点:延迟加载,效率高
//缺点:jdk1.5之后才可以用
//由于jdk1.5之前编译器允许处理器乱序执行,所以可能导致获取到没初始化完毕的instance
public class Singleton05 {

    private static Singleton05 instance = null