单例模式(Singleton Patten):确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例(Ensure a class has only one instance, and provide a global point of access to it)。程序员
Singleton类称为单例类,经过使用private的构造函数确保了在一个应用中只产生一个实例,而且是自行实例化的(在Singleton中本身使用new Singleton())。安全
饿汉式:在类加载时就建立对象实例,而无论实际是否须要建立。多线程
public class Singleton { private static final Singleton singleton = new Singleton(); private Singleton() { } public static Singleton getInstance() { return singleton; } }
懒汉式:只有调用getInstance的时候,才实例化对象。ide
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
问题:函数
方案1:源码分析
在getInstance()方法上加synchronized关键字。性能
public class Singleton { private static Singleton singleton; private Singleton() { } public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }
问题:只有在单例初始化的时候咱们才须要保证线程安全,其余时候方法上的synchronized关键字只会下降性能。测试
方案2:flex
只在单例初始化的时候加synchronized关键字。优化
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { singleton = new Singleton(); } } return singleton; } }
问题:仍是有可能会有多个进程同时经过(singleton == null)的条件检查,进而建立多个实例。
方案3:
public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance() { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } return singleton; } }
问题:和方案1相似,原本只是想让new这个操做并行,如今只要是进入getInstance()的线程都得同步,影响性能。
方案4:
双重检查加锁(Double-Check Lock)。
public class Singleton { private static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { //若是被同步的线程中,有一个线程建立了对象,那么别的线程就不用再建立了 if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
问题:DCL失效。
DCL失效主要在于singleton = new Singleton()
这句,这并不是是一个原子操做,事实上在JVM中这句话大概作了下面 3 件事情:
可是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序多是 1-2-3 也多是 1-3-2。若是是后者,则在3执行完毕、2未执行以前,若是另外一个线程抢占了锁,这时 instance 已是非 null 了(但却没有初始化),因此该线程会直接返回 instance,而后使用,而后瓜熟蒂落地报错。
方案5(最终版):
加volatile。
public class Singleton { private volatile static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
使用 volatile 有两个做用:
这个变量不会在多个线程中保存复本,而是直接从内存读取。
老版《Effective Java》中推荐的方式:
public class Singleton { private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton() { } public static final Singleton getInstance() { return SingletonHolder.INSTANCE; } }
上面这种方式,仍然使用JVM自己的机制保证了线程安全问题:
枚举实现:
public enum Singleton { INSTANCE; public void doSomething() { } }
默认枚举实例的建立是线程安全的,因此不须要担忧线程安全的问题。可是在枚举中的其余方法的线程安全由程序员本身负责。还有防止上面的经过反射机制调用私用构造器。
这个版本基本上消除了绝大多数的问题,代码也很是简单,是新版的《Effective Java》中推荐的模式。
单例实现采用方案5,序列化攻击代码以下:
public class Main { public static void main(String[] args) throws IOException, ClassNotFoundException { //序列化方式破坏单例 测试 serializeDestroyMethod(); } private static void serializeDestroyMethod() throws IOException, ClassNotFoundException { Singleton singleton; Singleton singletonNew; singleton = Singleton.getInstance(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(singleton); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); singletonNew = (Singleton) ois.readObject(); System.out.println(singleton == singletonNew); } }
打印结果为:false。在单例类中添加一个方法 readResolve():
public class Singleton implements Serializable { private volatile static Singleton singleton = null; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } private Object readResolve() { return singleton; } }
再执行攻击代码,打印结果为:true。
反序列化攻击源码分析:
//默认状况下 该方法经过反射建立一个新对象并返回 private Object readOrdinaryObject(boolean unshared) throws IOException { if (bin.readByte() != TC_OBJECT) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); desc.checkDeserialize(); Class<?> cl = desc.forClass(); if (cl == String.class || cl == Class.class || cl == ObjectStreamClass.class) { throw new InvalidClassException("invalid class descriptor"); } Object obj; try { obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } passHandle = handles.assign(unshared ? unsharedMarker : obj); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(passHandle, resolveEx); } if (desc.isExternalizable()) { readExternalData((Externalizable) obj, desc); } else { readSerialData(obj, desc); } handles.finish(passHandle); //通过上面的代码,新对象已经被new出来了 //下面hasReadResolveMethod()这个方法很关键。若是该类存在readResolve()方法,就调用该方法返回的实例替换掉新建立的对象。若是不存在就直接把new出来的对象返回出去。 if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } } return obj; }
... /** * 返回该类是否有readResolve方法 */ boolean hasReadResolveMethod() { requireInitialized(); return (readResolveMethod != null); }
因此在单例类中添加方法 readResolve(),就能够防范反序列化攻击。
单例实现依然采用方案5,反射攻击代码以下:
public class Main { public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { reflexDestroyMethod(); } private static void reflexDestroyMethod() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class objectClass = Singleton.class; Constructor constructor = objectClass.getDeclaredConstructor(); constructor.setAccessible(true); Singleton singleton = Singleton.getInstance(); Singleton singletonNew = (Singleton) constructor.newInstance(); System.out.println(singleton == singletonNew); } }
打印结果为:false。
使用枚举实现单例后,执行反射攻击报错以下:
缘由是Singleton.class.getDeclaredConstructors()获取全部构造器,会发现并无咱们所设置的无参构造器,只有一个参数为(String.class,int.class)构造器。看下Enum源码就明白,这两个参数是name和ordinal两个属性:
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable { private final String name; public final String name() { return name; } private final int ordinal; public final int ordinal() { return ordinal; } protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; } //余下省略
枚举Enum是个抽象类,一旦一个类声明为枚举,实际上就是继承了Enum,因此就会有(String.class,int.class)的构造器。既然无参构造方法找不到,那咱们就使用父类Enum的构造器,看看是什么状况:
public class Main { public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { //反射方式破坏单例 测试 reflexDestroyMethod(); } private static void reflexDestroyMethod() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class objectClass = SingletonE.class; Constructor constructor = objectClass.getDeclaredConstructor(String.class, int.class); constructor.setAccessible(true); SingletonE singleton = SingletonE.INSTANCE; SingletonE singletonNew = (SingletonE) constructor.newInstance("test", 1); System.out.println(singleton == singletonNew); } }
执行结果以下:
说是不能反射建立枚举对象,newInstance()方法源码以下:
@CallerSensitive public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } if ((clazz.getModifiers() & Modifier.ENUM) != 0) //看这一行 throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
由上文源码可知,反射在经过newInstance建立对象时,会检查该类是否ENUM修饰,若是是则抛出异常,反射失败。
单元素的枚举类型已经成为实现Singleton的最佳方法 —— 《Effective Java》