首页 > 代码库 > 单例模式的几种实现方式

单例模式的几种实现方式


单例模式是现如今非常普遍的模式之一。它是一种对象创建模式,用于生产一个对象的具体实例,它可以确保一个系统中一个类只产生一个实例。在java中,这样的行为带来两种好处:

1):对于频繁创建的对象,可以省略对象创建所花费的时间,对于一些重量级对象而言,是非常可观的系统开销。
2):由于new的操作减少,因而对系统内存的使用频率也会降低,这将减轻GC的压力,缩短GC的停顿时间。

因此对于系统的关键组件和被频繁使用的对象,使用单例模式可以有效改善系统性能。
下面介绍几种单例模式的写法:

1.饿汉模式
public Singleton {        private Singleton {        System.out.println("Singleton is create")//创建单例的过程可能会比较慢    }        private static Singleton instance = new Singleton();        public static Singleton getInstance() {        return instance;    }}

这是最为普遍的一种方法,简单易懂。注意代码中重点标注的部分,首先单例必须要有一个private访问级别的构造函数。只有这样才能保证不会在系统中其他代码中实例化,这点是相当重要的;其次instance成员变量和getInstance()方法必须是static的。使用者通过Singleton.getInstance()就可以获得相关单例(类加载时创建instance对象创建于静态内存区内并且只有一个拷贝。在这里注意静态方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。而且因为实例成员与特定的对象关联,只能访问所属类的静态成员变量和成员方法。)。

不过它的不足也显而易见,就是无法对instance实例做延时加载。假如单例的创建过程很慢,而由于instance成员变量时static定义的,因此JVM加载单例类时,单例对象就会在静态内存区里建立。如果此时此单例在系统中还扮演其他角色(就是用到其他静态方法或变量),那么任何使用这个单例类的时候都会初始化这个单例变量instance,而不管是否会被用到(即任何Singleton.otherStaticMethod()时候,都会执行其构造方法输出"Singleton is create"而造成不必要的时间和内存的开销)。为了解决这个问题我们需要引入一个延迟加载机制,就是下面的懒汉模式。

2.懒汉模式
public LazySingleton {        private LazySingleton {        System.out.println("LazySingleton is create")//创建单例的过程可能会比较慢    }        private static LazySingleton instance = null;        public static LazySingleton getInstanceNotSafe() {//线程不安全        if(instance = null) {            instance = new LazySingleton;        }        return instance;    }    public static synchronized LazySingleton getInstanceSafe() {//线程安全        if(instance = null) {            instance = new LazySingleton;        }        return instance;    }}

这里首先对于静态变量instance初始值赋予null,确保类加载时没有额外的负载。其次在getInstance工厂方法中,判断当前单例是否存在,若存在则返回不存在时再建立单例。注意上述中getInstanceNodSafe()方法不是同步的,故在多线程的环境下线程1正新建单例时,完成赋值操作,这时线程2判断instance为null,故线程2也将新建单例的程序,而导致多个实例被创建而导致非单例。所以在多线程的环境中使用引入同步关键字的getInstanceSafe()的方法,但是因为同步需要等待它的时耗远远大于饿汉模式。

以下测试代码说明了这个问题:
    public void run() {        for(int i = 0 ;i < 100000 ;i++) {            Singleton.getInstance();            //LazySingleton.getInstanceSafe();        }        System.out.println("spent:" + (System.currentTimeMillis() - begintime));    }

开启五个线程同时完成以上代码,饿汉模式的单例耗时0ms,而使用LazySingleton却相对耗时约390ms。性能上至少相差两个数量级。为了延迟加载引入了同步关键字后降低了性能,为使这个同步方法更为有效一个双重检查锁定的模式应运而生了。


3.双重检查锁定模式
public Singleton {        private Singleton {        System.out.println("Singleton is create")//创建单例的过程可能会比较慢    }        private static Singleton instance = null;        public static Singleton getInstance() {        if (instance == null) {            synchronized(Singleton.class) {          if(instance == null) {                   instance = new Singleton();          }            }        }     return instance;    }}     

此模式先判断instance是否为null,之后才进入同步语句。当第一个线程1与第二个线程2并发进入第一个if语句后,一个线程进入synchronized块来初始化instance而另一个线程则被阻断。当第一个线程退出synchronized块时,等待着的线程2进入并判断instance是否已创建再返回instance。至此对instance进行两次检查。这也是“双重检查锁定”名称的由来。与懒汉模式相比不用每次调用getinstance()都付出同步的代价,只有第一次创建才会同步,创建之后就没用了。

4.静态内部类模式
public class StaticInnerClassSingleton {    private StaticInnerClassSingleton () {        System.out.println("StaticInnerClassSingleton is create");    }       private static class SingletonHolder {      private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();//只会加载一次得到单个实例    }        public static StaticInnerClassSingleton getInstance() {      return SingletonHolder.instance;    }
}
在这个实现中,单例模式内部类来维护单例的实例。当StaticInnerClassSingleton加载时,其内部类不会被初始化,故StaticInnerClassSingleton类被载入JVM时,不会初始化单例类,而当getInstance()方法被调用时,才会加载SingletonHolder,从而初始化。同时,由于实例建立是在内部类加载时完成,故天生对多线程友好。getInstance()方法也不需要使用同步关键字。因此与饿汉模式相比等待延迟加载instance,所以不用担心只想调用其他静态方法时会创建一个单例。

5.总结与漏洞修复

通常情况下,用以上方式实现单例已经可以确保在系统中只存在唯一的实例,个人比较推荐用第三或第四种方法。但仍然有例外的情况,可能导致系统生成多个实例,比如在代码中,通过反射机制,强行调用单例类的私有构造函数生成多个单例,但是我们在这里先不讨论这种极端方式。但是仍有些合法的方法可能导致多个单例的产生,如:

1.如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2.如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。

对于第一个问题修复方法:
private static Class getClass(String classname) throws ClassNotFoundException {         
    ClassLoader classLoader
= Thread.currentThread().getContextClassLoader(); if(classLoader == null){    classLoader = Singleton.class.getClassLoader();   }
return (classLoader.loadClass(classname));
} }
对于第二个问题的修复方法:
public class Singleton implements java.io.Serializable {  
public static Singleton instance = new Singleton(); protected Singleton() { System.out.println("Singleton is create"); }
private Object readResolve() { //这里修复,阻止生成新的实例总是返回当前对象 return instance; }
}

 



 

单例模式的几种实现方式