单例三大要素
线程安全 延迟加载 序列化与反序列化安全
饿汉式 public class Singleton { private static Singleton = new Singleton(); private Singleton() {} public static getSignleton(){ return singleton; } } 懒汉式 android 源码中使用的方法 public class Singleton { private static Singleton singleton = null; private Singleton(){} public static Singleton getSingleton() { if(singleton == null) singleton = new Singleton(); return singleton; } } 双重检查锁 public class Singleton { private static volatile Singleton singleton = null; private Singleton(){} public static Singleton getSingleton(){ if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); //位置2 } } } return singleton; } }这种方式使用双重检查锁,多线程环境下执行getInstance()时先判断单例对象是否已经初始化,如果已经初始化,就直接返回单例对象,如果未初始化,就在同步代码块中先进行初始化,然后返回,效率很高。
但是这种方式是一个错误的优化,问题的根源出在位置2
sInstance =new Singleton();这句话创建了一个对象,他可以分解成为如下3行代码:
memory = allocate(); // 1.分配对象的内存空间 ctorInstance(memory); // 2.初始化对象 sInstance = memory; // 3.设置sInstance指向刚分配的内存地址上述伪代码中的2和3之间可能会发生重排序,重排序后的执行顺序如下
memory = allocate(); // 1.分配对象的内存空间 sInstance = memory; // 2.设置sInstance指向刚分配的内存地址,此时对象还没有被初始化 ctorInstance(memory); // 3.初始化对象因为这种重排序并不影响Java规范中的规范:intra-thread sematics允许那些在单线程内不会改变单线程程序执行结果的重排序。
有两种解决方案:一种接下来会讲到的静态内部类,这是因为在类初始化中JVM会自己去获取锁;另一种是volatile,将对象声明为volatitle后,前面的重排序在多线程环境中将会被禁止
public class Singleton { private static volatile Singleton sInstance; public static Singleton getInstance() { if (sInstance == null) { synchronized (Singleton.class) { if (sInstance == null) { sInstance = new Singleton(); } } } return sInstance; } private Singleton() {} } 静态内部类 使用懒汉式会带来同步的问题,使用饿汉式可以避免;同时由于静态内部类在被使用时才加载且只加载一次,又起到了延迟加载的作用 public class Singleton { private static class Holder { private static Singleton singleton = new Singleton(); } private Singleton(){} public static Singleton getSingleton(){ return Holder.singleton; } } 枚举 唯一满足三大要素的简单写法,但是内存消耗是静态变量的两倍 public enum Singleton { INSTANCE; private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; } }另外,通过反射获取构造函数,然后调用setAccessible(true)就可以调用私有的构造函数,所有e1和e2是两个不同的对象。 如果要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。