转载请注明原文地址: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; }
一、反序列化对象时会破环单例线程
反序列化对象时不会调用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等方法。