详解单例模式

来源:互联网 发布:js时间戳转换为日期 编辑:程序博客网 时间:2024/06/06 07:42

详解单例模式

单例的特点有:

  • 私有的构造方法
  • 指向自己实例的私有静态引用
  • 以自己实例为返回值的静态的公有的方法

众所周知的,单例模式有以下几种方式:

  • 懒汉式(线程安全版)
    等到第一次访问单例类的实例时才创建,可以看成延迟加载。
public class Singleton {    private static Singleton instance;    private Singleton () {}    public static synchronized Singleton getInstance() {        if (instance == null) {            instance = new Singleton ();        }        return instance;    }} 
  • 懒汉式-双重校验锁
    所 谓“双重检查加锁”机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进 行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需 要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
    “双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
public class Singleton {private volatile static Singleton instance = null;private Singleton(){}public static Singleton getInstance(){//先检查实例是否存在,如果不存在才进入下面的同步块        if(instance == null){//同步块,线程安全的创建实例            synchronized (Singleton.class) {//再次检查实例是否存在,如果不存在才真正的创建实例                if(instance == null){                    instance = new Singleton();                }            }        }return instance; }

禁止指令重排优化这条语义直到jdk1.5以后才能正确工作。此前的JDK中即使将变量声明为volatile也无法完全避免重排序所导致的问题。所以,在jdk1.5版本前,双重检查锁形式的单例模式是无法保证线程安全的。

  • 饿汉式
    基于classloder机制避免了多线程同步问题。不过instance在类加载时就实例化,大多数情况是调用getInstance方法导致类被加载。可以看成预加载。
public class Singleton {    private static Singleton instance = new Singleton ();    private Singleton () {}    public static Singleton getInstance() {        return instance;    }} 
  • 静态内部类【推荐】
    这种方式相同利用了classloder的机制来保证初始化instance时只有一个线程,前面饿汉式中Singleton被装载是instance一定初始化。而这种方法Singleton被装载时,只有主动去使用getInstance方法是,才会装载SingletonHolder ,进而实例化instance。他的好处就是如果实例化instance很浪费资源,那么意味着延迟加载时一种好的选择。
public class Singleton {    private static class SingletonHolder {        private static final Singleton instance = new Singleton();    }    private Singleton() {}    public static final Singleton getInstance() {        return SingletonHolder.instance;    }}
  • 枚举

这是一种很值得提倡的方式,不仅仅避免了多线程同步问题,还能反序列化重新创建新的对象。

1.它不提供构造函数杜绝用户产生该类实例;

2.全部实例都是public static final不允许任何改动却可以让用户使用;

3.所有枚举值都是唯一的;

public emun Singleton {   //定义一个枚举的元素,它就代表了Singleton的一个实例   uniqueInstance;   //行为方法   public void singletonOperation(){       //功能处理   }}

上述单例模式的总结

不管饿汉式、懒汉式、双重校验锁还是静态内部类都有两个共同的缺点:

  • 都需要额外的工作(Serializable、transient、readResolve())来实现序列化,否则每次反序列化一个序列化的对象实例时都会创建一个新的实例。
  • 可能会有人使用反射强行调用我们的私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。
    而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,Effective Java推荐尽可能地使用枚举来实现单例。
    但是需要注意的是,Enums的内存消耗通常是static constants的2倍。你应该尽量避免在Android上使用enums。

单例的优缺点

  • 主要优点:

    1. 提供了对唯一实例的受控访问。
    2. 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
    3. 允许可变数目的实例。
  • 主要缺点:

    1. 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
    2. 单例类的职责过重,在一定程度上违背了“单一职责原则”。
    3. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

部分内容参考: http://stormzhang.com/designpattern/2016/04/04/singleton-extend/

原创粉丝点击