单例模式,也叫单子模式,是一种常用的软件设计模式,属于创建型模式的一种。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。——《维基百科》
单例模式在开发中应用非常广泛,某些对象的构造可能会非常耗费资源,在整个软件周期中我们只想让其存在一个实例,或者作为全局通用对象,我们希望它在任意地方的访问取得一致的结果,那就可以使用单例模式,像Servlet,Spring中的Bean默认都是单例的。单例模式的实现方式有好几种,本文主要记录在Java下单例模式的具体实现方式。
饿汉式
1 2 3 4 5 6 7 8 9 10
| public class Singleton { private static final Singleton INSTANCE = new Singleton();
private Singleton() { }
public static Singleton getInstance() { return INSTANCE; } }
|
饿汉式是在类加载时就完成了单例的初始化,使用时直接获取。基于类的加载机制避免了多线程环境下的同步问题,但是初始化的实例可能并未使用,导致资源浪费,同时也无法避免反射,反序列化带来的影响。
懒汉式
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Singleton { private static Singleton INSTANCE;
private Singleton() { }
public synchronized static Singleton getInstance() { if (INSTANCE == null) { INSTANCE = new Singleton(); } return INSTANCE; } }
|
懒汉式是在第一次获取实例的时候进行单例初始化,对获取方法加synchronized同步锁,保证了线程安全,也存在反射和反序列化的问题。
双重检查锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Singleton { private volatile static Singleton INSTANCE;
private Singleton() { }
public static Singleton getInstance() { if (INSTANCE == null) { synchronized (Singleton.class) { if (INSTANCE == null) { INSTANCE = new Singleton(); } } } return INSTANCE; } }
|
大名鼎鼎的DCL,饿汉式的优化版本,使用双重检查锁保证了线程安全,同时降低了在多线程环境下的加锁竞争,volatile关键字的应用典范(变量多线程可见性)。
静态内部类
1 2 3 4 5 6 7 8 9 10 11 12
| public class Singleton { private Singleton() { }
public static Singleton getInstance() { return SingletonHolder.INSTANCE; }
private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } }
|
静态内部类的方式较之前的几种都有了不小的提升,因为基于类的加载机制进行初始化,防止了线程安全的问题,同时只有在调用获取实例方法时才会触发第一次加载,也实现了懒加载机制,写法也比较简单,算是一种比较良好的实现方式了。
枚举
1 2 3
| public enum Singleton { INSTANCE; }
|
嗯,枚举实现单例就是这么简单且纯粹。同时基于对枚举的特殊处理机制,保证了线程安全,也规避了反射和反序列化对单例带来的破坏,这是我最常用来实现单例的方式。
反射和反序列化对单例的破坏
反射破坏单例模式
以静态内部类为例,反射获取单例类的构造器,变更构造器的访问权限之后可以直接实例化,生成与单例接口返回不一致的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class Singleton { private Singleton() { }
public static Singleton getInstance() { return SingletonHolder.INSTANCE; }
private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); }
public static void main(String[] args) throws Exception { Class<Singleton> clazz = Singleton.class; Constructor<Singleton> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); Singleton refObject = constructor.newInstance(); System.out.println("singleton:" + Singleton.getInstance().hashCode()); System.out.println("refObject:" + refObject.hashCode()); System.out.println("equals:" + (refObject == Singleton.getInstance())); } }
|
想要阻止反射对单例的破坏,可行的方法是在构造器中抛出异常,禁止反射对构造器的调用。
1 2 3 4 5 6
| private Singleton() { if (SingletonHolder.INSTANCE != null) { throw new RuntimeException("not allow"); } }
|
反序列化破坏单例
还是以静态内部类为例,对象实现序列化接口后,对其进行序列化,把它写入文件中,然后再反序列化读出来,生成与单例接口返回不一致的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class Singleton implements Serializable { private Singleton() { }
public static Singleton getInstance() { return SingletonHolder.INSTANCE; }
private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); }
public static void main(String[] args) throws Exception { String path = System.getProperty("user.dir") + "/file"; try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(path))) { out.writeObject(Singleton.getInstance()); } try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(path))) { Singleton inObject = (Singleton) in.readObject(); System.out.println("singleton:" + Singleton.getInstance().hashCode()); System.out.println("inObject:" + inObject.hashCode()); System.out.println("equals:" + (inObject == Singleton.getInstance())); } } }
|
阻止反序列化对单例的破坏,可以禁止序列化,禁止反序列化,也可以重写readResolve()方法,使其返回单例对象。
禁止序列化:
1 2 3 4
| private void writeObject(ObjectOutputStream oos) throws IOException { throw new RuntimeException("not allow"); }
|
禁止反序列化:
1 2 3 4
| private void readObject(ObjectInputStream ois) { throw new RuntimeException("not allow"); }
|
也可以在构造器中禁止反序列化,因为反序列化时,会利用反射调用类中的构造器:
1 2 3 4 5 6
| private Singleton() { if (SingletonHolder.INSTANCE != null) { throw new RuntimeException("not allow"); } }
|
重写readResolve()方法,直接返回单例对象:
1 2 3
| private Object readResolve() { return SingletonHolder.INSTANCE; }
|
枚举单例为什么可以防止反射和序列化的破坏
这是Constructor中生成实例的方法,可以看到当类对象是枚举时,会直接抛出IllegalArgumentException异常,禁止反射生成枚举的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects"); T inst = (T) ca.newInstance(initargs); return inst; }
|
在ObjectInputStream类中,反序列化枚举对象时,会调用readEnum()方法,然后调用Enum类的valueOf()方法直接返回枚举的实例,所以可以保证实例的唯一性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| private Enum<?> readEnum(boolean unshared) throws IOException { if (cl != null) { try { @SuppressWarnings("unchecked") Enum<?> en = Enum.valueOf((Class)cl, name); result = en; } catch (IllegalArgumentException ex) { throw (IOException) new InvalidObjectException( "enum constant " + name + " does not exist in " + cl).initCause(ex); } if (!unshared) { handles.setObject(enumHandle, result); } } return result; }
|
1 2 3 4 5 6 7 8 9 10 11
| public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) { T result = enumType.enumConstantDirectory().get(name); if (result != null) return result; if (name == null) throw new NullPointerException("Name is null"); throw new IllegalArgumentException( "No enum constant " + enumType.getCanonicalName() + "." + name); }
|