码迷,mamicode.com
首页 > 其他好文 > 详细

单例模式的几种实现方式

时间:2014-09-16 18:45:30      阅读:581      评论:0      收藏:0      [点我收藏+]

标签:style   blog   color   io   使用   java   strong   for   div   


单例模式是现如今非常普遍的模式之一。它是一种对象创建模式,用于生产一个对象的具体实例,它可以确保一个系统中一个类只产生一个实例。在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; }
}

 




 

单例模式的几种实现方式

标签:style   blog   color   io   使用   java   strong   for   div   

原文地址:http://www.cnblogs.com/leetieniu2014/p/3975417.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!