设计模式-单例模式

设计模式 单例模式

在开发中经常会用到单例设计模式,目的就是为了在程序的整个生命周期内,只会创建一个类的实例对象,而且只要程序不被杀死,该实例对象就不会被释放。 指在 JVM 范围内。

定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点

优缺点

1、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间;
2、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用;
3、单例可长驻内存,减少系统开销。

单例模式的应用举例:
1、生成全局惟一的序列号;
2、访问全局复用的惟一资源,如磁盘、总线等;
3、单个对象占用的资源过多,如数据库等;
4、系统全局统一管理,如Windows下的Task Manager;
5、网站计数器。

缺点:
1、单例模式的扩展是比较困难的;
2、赋于了单例以太多的职责,某种程度上违反单一职责原则(六大原则后面会讲到);
3、单例模式是并发协作软件模块中需要最先完成的,因而其不利于测试;
4、单例模式在某种情况下会导致“资源瓶颈”。

实现方式:

在单例的类中设置一个 private 静态变量instance,instance 类型为当前类,用来持有单例唯一的实例。
将(无参数)构造器设置为 private,避免外部使用 new 构造多个实例。
提供一个 public 的静态方法,如 getInstance,用来返回该类的唯一实例 instance。

  • 懒汉式 lazy initialization
  • 饿汉式 eager initialization
  • 双重校验锁 Double CheckLock DCL
  • 枚举
  • 静态内部类

懒汉式

懒汉模式是声明一个静态对象,并且在用户第一次调用getInstance时进行初始化,而上述的饿汉模式是在声明静态对象时就已经初始化,

public class Singleton {

    private static Singleton instance = null;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

synchronized 保证 线程安全。

延迟初始化

饿汉式

public class Singleton {

private static final Singleton instance = new Singleton();

private Singleton(){}

public static Singleton getInstance(){
    return instance;
}

}

不足:

如果构造方法中存在过多的处理,会导致加载这个类时比较慢,可能引起性能问题。
如果使用饿汉式的话,只进行了类的装载,并没有实质的调用,会造成资源的浪费。

Double CheckLock DCL

public class SingleDCL {

    private volatile static SingleDCL singleDCL = null;
    private SingleDCL(){}

    public static SingleDCL getSingleDCL(){

        if(singleDCL == null){

            synchronized (SingleDCL.class){
                if(singleDCL == null){
                    singleDCL = new SingleDCL();
                }
            }
        }
        return singleDCL;
    }

}

假设线程A执行到了instance=new Singleton2 ()语句,这里看起来是一句代码,但实际上它并不是一个原子操作,什么是原子操作?可以看这儿(http://baike.baidu.com/item/原子操作?fr=aladdin),这句代码最终会被编译成多条汇编指令,大致做了三件事:

    (1)、给Singleton的实例分配内存;

    (2)、调用Singleton()的构造函数,初始化成员字段;

    (3)、将instance对象指向分配的内存空间(此时instance就不是null);

    DCL的优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。

DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于jdk1.6版本下使用,否则这种方式一般能够满足需求。

《Java并发编程实战》 p286 16.2.4 这种方式已被废弃。

静态内部类单例

DCL 有可能失效

枚举单例

写法简单是枚举单例最大的优点,枚举在Java中与普通的类是一样的,不仅能够有字段,还能够有自己的方法。最重要的是默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。为什么?在上述的几种单例中,在一个情况下它们会出现重新创建对象的情况,那就是反序列化。

通过反序列化可以将一个单例的实例对象写到磁盘,然后再读回来,从而有效获得一个实例。即使构造函数是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于调用该类的构造函数。反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的、被实例化的方法readResolve,这个方法可以让开发人员控制对象的反序列化。例如:上述几个实例中如果要杜绝单例对象在被饭序列化重新生成对象,那么必须加入如下方法:
/**
 * 枚举单例
 * Created by Brady on 2017/5/23.
 */
public enum SingleEnum {
    INSTANCE; 
}

容器实现单例

在程序的初始化时,将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时也可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

    不管以哪种形式实现单例模式,他们的核心原理都是讲构造函数私有化,并且通过静态方法获取一个唯一的实例,在这个获取的过程中必须保证线程安全、防止反序列化导致重新生成实例对象等问题。选择哪种实现方式取决于项目本身,如是否是复杂的并发环境、jdk版本是否过低、单例对象的资源消耗等。

参考资料

设计模式系列 14-- 单例模式
Java设计模式(十) 你真的用对单例模式了吗?
Android设计模式——单例模式之源码使用场景(一)
单例模式的七种写法
1、Python与设计模式--单例模式

原文地址:https://www.cnblogs.com/ironbrady/p/6961411.html