[置顶] 设计模式(二)单例模式

概念

单例模式也被称作单件模式(单体模式),主要作用是控制某个类型的实例在应用中是唯一的,还提供了一个全局唯一访问这个类实例的访问点getInstance方法。单例模式是对象的创建模式之一,此外还包括工厂模式。

单例模式的特点

  • 该类只有一个实例
  • 该类自行创建实例(改类内部创建自身的实例对象)
  • 想整个系统公开实例接口(类构造方法私有化)
使用范围:目前java里面实现的单例是一个ClassLoader及其子ClassLoader的访问,也就是说如果一个虚拟机里有多个ClassLoader,而这些ClassLoader都加载某个类的话,就算这个类时单例,也会产生多个实例。如果一个一个机器上有多个虚拟机,那么每个虚拟机里面至少有一个这个类的实例,对于整个机器来说就不是单例了。

使用意义:有些情况需要一个全局变量(如计数器),如果实例多的话,计数会冲突;还有一些配置文件,可能多个地方会用到,如果是不是单例模式,在每个用的地方都创建实例对象的话,系统中会同时存在多份相同的配置文件,这会浪费内存资源。

注意事项:这里讨论的单例不适用于集群环境。

建议单例模式的方法命名为getInstance()。该方法返回值是单例类的类型,方法可以由参数。

单例模式的几种类型

懒汉式单例

类加载的时候不创建实例,只在第一次请求实例的时候创建,并且只创建一次,但由于线程同步会降低访问速度。

/**
 * 懒汉式单例模式示例
 */
class LazySingleton{
    //私有静态对象,类加载的时候不做初始化
    private static LazySingleton instance = null;
    //私有构造方法,避免外部创建实例
    private LazySingleton(){}
    //静态工厂方法,返回此类的唯一实例,实例没有初始化时才初始化
    synchronized public static LazySingleton getInstance(){
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

  • 构造方法私有化:避免类外部通过构造方法创建多个实例
  • 提供获取实例的方法:构造方法私有化后外部不能创建实例,此时,让类提供一个方法来返回类的实例
  • 获取实例的方法是静态的:客户端需要调用这个方法就要的先得到类实例,可是这个方法就是为了返回类实例,避免武侠循环,在该方法添加static关键字,就可以通过类名直接访问
  • 定义好存储实例的属性:如果直接 return new Singlenton() 返回实例,每次客户端调用时,都将产生一个新的实例,这样肯定会有多个实例。单例模式可以用一个属性来记录创建好的类实例,第一次创建时记录下来,以后就可以复用
  • 把这个属性也定义成静态的:由于在一个静态方法里使用,这一这个属性被迫成为一个类变量
  • 线程安全:降低访问速度,而且每次访问都需要判断一次。为了更好的实现,采用“双重检查加锁”(不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后先检查实例是否存在,如果不存在才进行下面的同步块,,这是第一重检查,进入同步块后,再次检查实例是否存在。如果不存在,就在同步的情况下创建实例,这是第二重检查。这样的话整个过程只需要一次同步,从而减少了多次在同步情况下进行判断所浪费的时间)
  • 使用“双重检查加锁”机制时,需要添加volatile关键字,使用在Java5以上版本、
class LazySingleton{
    private volatile static LazySingleton instance = null;
    private LazySingleton(){}
    public static LazySingleton getInstance(){
        if (instance == null) {
            synchronized (LazySingleton.class){
                if(instance == null){
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

饿汉式单例

类被加载的时候唯一的实例已经被创建,不能实现延迟加载

/**
 * 饿汉式单例模式示例
 */
class Singleton{
    //私有静态变量存储创建好的实例
    private static Singleton instance = new Singleton();
    //private static final Singleton instance = new Singleton();可以定义为static final成员
    //私有构造方法,避免外部创建实例
    private Singleton(){}
    //微客户端提供类实例
    public static Singleton getInstance(){
        return instance;
    }
}

存储实例的属性是静态的,利用了static的特性

  • static变量在类装载的时候进行初始化
  • 多个实例的static变量会共享同一块内存区域

登记式单例

维护的是一组单例类的实例,将这些实例存放在一个map(登记簿),已登记的实例,从工厂直接返回,没登记的,则先登记再返回。

public class Singleton {
    //登记簿,用来存放所有登记的实例
    private static Map<String, Singleton> registry = new HashMap<>();
    //在类加载的时候添加一个实例到登记簿
    static {
        Singleton x = new Singleton();
        registry.put(x.getClass().getName(), x);
    }
    //受保护的默认构造方法
    protected Singleton(){}
    //静态工厂方法,放回指定等级对象的唯一实例
    public static Singleton getInstance(String name){
        if(name == null){
            name = "Singleton";
        }
        if (registry.get(name) == null){
            try {
                registry.put(name, (Singleton) Class.forName(name).newInstance());
            } catch (InstantiationException e){
                e.printStackTrace();
            } catch (ClassNotFoundException e){
                e.printStackTrace();
            } catch (IllegalAccessException e){
                e.printStackTrace();
            }
        }
        return registry.get(name);
    }
    
}

另一种实现单例模式的方式

常见的两种实现方式都存在小小的缺陷,既能实现延迟加载,又能实现线程安全的方式 Lazy initialization holder class 模式,该模式综合使用了java的类级内部类和多线程默认同步锁的知识,巧妙的实现了延迟加载和线程安全。

  • 静态初始化器方式:简单的实现了线程安全,可以由JVM保证线程安全性,但这种方式会在类装载的时候初始化对象,不管你需不需要,浪费一定的空间。
  • 类级内部类:能够让类装载的时候不去初始化对象,在这个类级内部类里去创建对象实例,只要不适用内部类,就不会创建对象实例。
public class SingletonPattern {
    /**
     * 类级内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     * 没有绑定关系,而且只有被调用的时候才会被装载,从而实现了延迟健在
     */
    private static class SingletonHolder{
        //静态初始化器,由JVM保证线程安全
        private static SingletonPattern instance = new SingletonPattern();
    }
    private SingletonPattern(){}
    
    public static SingletonPattern getInstance(){
        return SingletonHolder.instance;
    }    
}

当getInstance方法第一次被调用时,它第一次读取SingletonHolder.instance,使SingletonHolder类得到初始化,这个类被装载并初始化的时候,会初始化它的静态域,从而创建SingletionPattern实例,由于是静态域,只有在虚拟机在装载类的时候初始化一次,并由虚拟机来保证它的线程安全。

单例模式优缺点

  • 内存中只有一个实例,减小了内存消耗,减少系统性能开销,例如读写配置
  • 避免对资源的多重占用,例如文件的读写操作
  • 可以在系统设置全局的访问点,优化共享资源的访问
  • 单例模式没有接口,扩展困难,如果要扩展就要修改代码
  • 不利于测试,如果在并行开发环境中,单例模式没有完成,是没办法进行测试的,不能通过接口或者mock的方式虚拟对象
  • 没有实现单一只能原则,把“要单例”和业务逻辑融合在一起

使用场景

  • 要生成唯一序列号
  • 整个项目中需要一个共享访问点或共享数据,如web页面的计数器
  • 创建一个对象需要消耗的资源过多,如要访问I/O,访问数据库等资源
  • 需要定义大量的静态常量和静态方法(如工具类)

posted @ 2015-11-26 09:47  小鲜肉成长记  阅读(113)  评论(0编辑  收藏  举报