Java单例模式的几种实现

 

转载请注明原文地址:http://www.javashuo.com/article/p-xfppzqug-dv.htmlhtml

 

一:静态内部类实现单例模式

  原理:经过一个静态内部类定义一个静态变量来持有当前类实例,在类加载时就建立好,在使用时获取。java

  缺点:没法作到延迟建立对象,在类加载时进行建立会致使初始化时间变长。多线程

public class SingletonInner {
    private static class Holder {
        private static SingletonInner singleton = new SingletonInner();
    }
 
    private SingletonInner(){}
 
    public static SingletonInner getSingleton(){
        return Holder.singleton;
    }

 

二:饿汉模式

  原理:建立好一个静态变量,每次要用时直接返回。并发

  缺点:没法作到延迟建立对象,在类加载时进行建立会致使初始化时间变长。函数

public class SingletonHungry {
 
    private static SingletonHungry instance = new SingletonHungry();
 
    private SingletonHungry() {
    }
 
    public static SingletonHungry getInstance() {
        return instance;
    }

 

 

 

三:懒汉模式

  原理:延迟建立,在第一次用时才建立,以后每次用到就返回建立好的。性能

  缺点:因为synchronized的存在,多线程时效率很低。this

public class SingletonLazy {
 
    private static volatile SingletonLazy instance;
 
    private SingletonLazy() {
    }
 
    public static synchronized SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

 

 

四:双重校验锁懒汉模式

  原理:在getSingleton()方法中,进行两次null检查。这样能够极大提高并发度,进而提高性能。spa

public class Singleton {
    private static volatile Singleton singleton = null;//使用valatile,使该变量能被全部线程可见
 
    private Singleton(){}
 
    public static Singleton getSingleton(){
        if(singleton == null){
            synchronized (Singleton.class){//初始化时,加锁建立
                if(singleton == null){//为避免在加锁过程当中被其余线程建立了,再做一次非空校验
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }    

 

五:上述4种方式实现单例模式的缺点

  一、反序列化对象时会破环单例线程

  反序列化对象时不会调用getXX()方法,因而绕过了确保单例的逻辑,直接生成了一个新的对象,破环了单例。code

  解决办法是:重写类的反序列化方法,在反序列化方法中返回单例而不是建立一个新的对象。

public class Singleton implements java.io.Serializable {     
   public static Singleton INSTANCE = new Singleton();     
 
   protected Singleton() {     
   }  
 
   //反序列时直接返回当前INSTANCE
   private Object readResolve() {     
            return INSTANCE;     
      }    
}

  二、在代码中经过反射机制,直接调用类的私有构造函数建立新的对象,会破环单例

  解决办法是:维护一个volatile的标志变量在第一次建立实例时置为false;重写构造函数,根据标志变量决定是否容许建立。

private static volatile  boolean  flag = true;
private Singleton(){
    if(flag){
    flag = false;   //第一次建立时,改变标志
    }else{
        throw new RuntimeException("The instance  already exists !");
    }


六:枚举模式实现单例——将单例维护在枚举类中做为惟一实例

  原理:定义枚举类型,里面只维护一个实例,以此保证单例。每次取用时都从枚举中取,而不会取到其余实例。

public enum  SingletonEnum {
    INSTANCE;
    private String name;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }

  优势:

  1)使用SingletonEnum.INSTANCE进行访问,无需再定义getInstance方法和调用该方法。

  2)JVM对枚举实例的惟一性,避免了上面提到的反序列化和反射机制破环单例的状况出现:每个枚举类型和定义的枚举变量在JVM中都是惟一的。 

    缘由:枚举类型在序列化时仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是经过java.lang.Enum的valueOf方法来根据名字查找枚举对象

      同时,编译器禁止重写枚举类型的writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。

相关文章
相关标签/搜索