/** * 堆代码 duidaima.com * 饿汉式单例 */ public class HungrySingleton { //类初始化的时候便进行对象实例化 private static final HungrySingleton hungrySingleton = new HungrySingleton(); private HungrySingleton() { } public static HungrySingleton getInstance() { return hungrySingleton; } }优点:
/** * 饿汉式单例 */ public class LazySimpleSingleton { private static LazySimpleSingleton instance; private LazySimpleSingleton() { } public static LazySimpleSingleton getInstance() { // 如果实例不存在,则进行初始化 if (instance == null) { instance = new LazySimpleSingleton(); } return instance; } }上述代码在单线程下能够完美运行,但是在多线程下存在安全隐患。大家可以使用IDEA进行手动控制线程执行顺序来跟踪内存变化,下面我用图解的形式进行多线程下3种情形的说明。
情形1:
每个线程依次执行getInstance方法,得到的结果正是我们所期望的
情形2:
此种情形下,该种写法的单例模式会出现多线程安全问题,得到两个完全不同的对象
情形3:
该种情形下,虽然表面上最终得到的对象是同一个,但是在底层上其实是生成了2个对象,只不过是后者覆盖了前者,不符合单例模式绝对只有一个实例的要求。
/** * 饿汉式单例-同步锁 */ public class LazySynchronizedSingleton { private static LazySynchronizedSingleton instance; private LazySynchronizedSingleton() { } //添加synchronized关键字 public synchronized static LazySynchronizedSingleton getInstance() { if (instance == null) { instance = new LazySynchronizedSingleton(); } return instance; } }升级之后的程序能完美地解决线程安全问题。但是用synchronized加锁时,在线程数量较多的情况下,会导致大批线程阻塞,从而导致程序性能大幅下降
/** * 双重检查锁 */ public class LazyDoubleCheck { // 需要添加 volatile 关键字 private volatile static LazyDoubleCheck instance; private LazyDoubleCheck() { } public static LazyDoubleCheck getInstance() { //一重检查:检查实例,如果不存在,进入同步区块 if (instance == null) { synchronized (LazyDoubleCheck.class) { //双重检查:进入同步区块后,再检查一次,如果仍然是null,才创建实例 if (instance == null) { instance = new LazyDoubleCheck(); } } } return instance; } }第一重检查是为了确认instance是否已经被实例化,如果是,则无需再进入同步代码块,直接返回实例化对象,否则进入同步代码块进行创建,避免每次都排队进入同步代码块影响效率;
/** * 静态内部类实现单例 */ public class LazyStaticInnerClassSingleton { private LazyStaticInnerClassSingleton() { } public static final LazyStaticInnerClassSingleton getInstance() { return LazyHolder.LAZY; } // 静态内部类,未被使用时,是不会被加载的 private static class LazyHolder { private static final LazyStaticInnerClassSingleton LAZY = new LazyStaticInnerClassSingleton(); } }用静态内部类实现的单例本质上是一种懒汉式,因为在执行getInstance中的LazyHolder.LAZY语句之前,静态内部类并不会被加载。这种方式既避免了饿汉式单例的内存浪费问题,又摆脱了synchronized关键字的性能问题,同时也不存在线程安全问题。
/** * 利用反射破坏单例 */ public class SingletonBrokenByReflect { public static void main(String[] args) { try { Class<?> clazz = LazyStaticInnerClassSingleton.class; //通过反射弧获取类的私有构造方法 Constructor c = clazz.getDeclaredConstructor(null); //强制访问 c.setAccessible(true); Object obj1 = c.newInstance(); Object obj2 = c.newInstance(); //输出false System.out.println(obj1 == obj2); } catch (Exception e) { e.printStackTrace(); } } }如此,我们便使用反射破坏了单例。现在我们以静态内部类单例为例,解决这个问题。我们在构造方法中添加一些限制,一旦检测到对象已经被实例化,但是构造方法仍然被调用时直接抛出异常。
/** * 静态内部类实现单例 */ public class LazyStaticInnerClassSingleton { private LazyStaticInnerClassSingleton() { if (LazyHolder.LAZY != null) { throw new RuntimeException("实例被重复创建"); } } public static final LazyStaticInnerClassSingleton getInstance() { return LazyHolder.LAZY; } // 静态内部类,未被使用时,是不会被加载的 private static class LazyHolder { private static final LazyStaticInnerClassSingleton LAZY = new LazyStaticInnerClassSingleton(); } }7. 序列化破坏单例
/** * 可序列化的单例 */ public class SeriableSingleton implements Serializable { //类初始化的时候便进行对象实例化 private static final SeriableSingleton hungrySingleton = new SeriableSingleton(); private SeriableSingleton() { } public static SeriableSingleton getInstance() { return hungrySingleton; } } /** * 序列化破坏单例 */ public class SingletonBrokenBySerializing { public static void main(String[] args) { SeriableSingleton s1 = SeriableSingleton.getInstance(); SeriableSingleton s2 = null; FileOutputStream fos = null; try { File file; fos = new FileOutputStream("SeriableSingleton.obj"); OutputStream out; ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s1); oos.flush(); oos.close(); fos.close(); FileInputStream fis = new FileInputStream("SeriableSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); s2 = (SeriableSingleton) ois.readObject(); ois.close(); fis.close(); //输出为false System.out.println(s1 == s2); } catch (Exception e) { } } }从运行结果上看,反序列化和手动创建出来的对象是不一致的,违反了单例模式的初衷。那到底如何保证在序列化的情况下也能够实现单例模式呢,其实很简单,只需要增加一个readResolve方法即可。
public class SeriableSingleton implements Serializable { //类初始化的时候便进行对象实例化 private static final SeriableSingleton hungrySingleton = new SeriableSingleton(); private SeriableSingleton() { } public static SeriableSingleton getInstance() { return hungrySingleton; } //只需要添加这一个函数即可 private Object readResolve() { return hungrySingleton; } }实现的原理涉及到ObjectInputStream的源码,不属于本文的研究重点,如果读者需要,我可以另开一篇来进行讲解。
/* * 枚举式单例1 */ public class EnumSingleObject { private EnumSingleObject() { } enum SingletonEnum { INSTANCE; private EnumSingleObject instance; private SingletonEnum() { instance = new EnumSingleObject(); } public EnumSingleObject getInstance() { return INSTANCE.instance; } } //对外暴露一个获取EnumSingleObject对象的静态方法 public static EnumSingleObject getInstance() { return SingletonEnum.INSTANCE.getInstance(); } }枚举式的写法为什么可以实现我们的单例模式呢,我们首先使用javac EnumSingleObject.java生成EnumSingleObject.class文件,用反编译工具Jad在.class所在的目录下执行 jad EnumSingleObject.class命令,得到EnumSingleObject.jad文件,代码如下
static final class EnumSingleObject$SingletonEnum extends Enum { public static EnumSingleObject$SingletonEnum[] values() { return (EnumSingleObject$SingletonEnum[]) $VALUES.clone(); } public static EnumSingleObject$SingletonEnum valueOf(String s) { return (EnumSingleObject$SingletonEnum) Enum.valueOf(com / chanmufeng / Singleton / registerSingleton / EnumSingleObject$SingletonEnum, s); } public EnumSingleObject getInstance() { return INSTANCE.instance; } public static final EnumSingleObject$SingletonEnum INSTANCE; private EnumSingleObject instance; private static final EnumSingleObject$SingletonEnum $VALUES[]; // 该static代码块是枚举写法能够实现单例模式的关键 static { INSTANCE = new EnumSingleObject$SingletonEnum("INSTANCE", 0); $VALUES = (new EnumSingleObject$SingletonEnum[]{ INSTANCE }); } private EnumSingleObject$SingletonEnum(String s, int i) { super(s, i); instance = new EnumSingleObject(); } }其实,枚举式单例在静态代码块中就为INSTANCE进行了赋值,是一种饿汉式单例模式的体现,只不过这种饿汉式是JDK底层为我们做的操作,我们只是利用了JDK语法的特性罢了。
//测试序列化能否破坏 public static void main(String[] args) { EnumSingleObject s1 = EnumSingleObject.getInstance(); EnumSingleObject s2 = null; FileOutputStream fos = null; try { fos = new FileOutputStream("SeriableSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s1); oos.flush(); oos.close(); fos.close(); FileInputStream fis = new FileInputStream("SeriableSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); s2 = (EnumSingleObject) ois.readObject(); ois.close(); fis.close(); //输出为false System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } }很遗憾,序列化依然会破坏枚举式单例EnumSingleObject。What???不是说枚举式单例非常的优雅吗?连_Effective Java_都推荐使用吗?别急,接下来我们观察另一种写法:
/** * 枚举式单例2 */ public enum EnumSingleObject2 { INSTANCE; private Object data; public Object getData() { return data; } public void setData(Object data) { this.data = data; } public static EnumSingleObject2 getInstance() { return INSTANCE; } }我们再来进行序列化测试
public static void main(String[] args) { EnumSingleObject2 s1 = EnumSingleObject2.getInstance(); s1.setData(new Object()); EnumSingleObject2 s2 = null; FileOutputStream fos = null; try { fos = new FileOutputStream("SeriableSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s1); oos.flush(); oos.close(); fos.close(); FileInputStream fis = new FileInputStream("SeriableSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); s2 = (EnumSingleObject2) ois.readObject(); ois.close(); fis.close(); //输出为true System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } }打印结果为true,说明枚举式单例2的写法可以防止序列化破坏。而很多文章和博客用的往往是第1种写法,下面我们解释这两种写法的区别.
public final Object readObject() throws IOException, ClassNotFoundException { ... try { Object obj = readObject0(false); ... return obj; } finally { ... } }在readObject()方法中又调用了readObject0()方法
private Object readObject0(boolean unshared) throws IOException { ... //枚举式单例1的程序会进入到这里 case TC_CLASS: return readClass(unshared); ... //枚举式单例2的程序会进入到这里 case TC_ENUM: return checkResolve(readEnum(unshared)); }我们先看一下readEnum()方法
private Enum<?> readEnum(boolean unshared) throws IOException { ... String name = readString(false); Enum<?> result = null; Class<?> cl = desc.forClass(); if (cl != null) { try { @SuppressWarnings("unchecked") //!!!!这里是重点 Enum<?> en = Enum.valueOf((Class)cl, name); result = en; } catch (IllegalArgumentException ex) { ... } } ... return result; }到这里我们发现,枚举类型其实通过类名和类对象找到唯一一个枚举对象,因此,枚举对象不会被类加载器加载多次。而readClass()并无此功能。
public static void main(String[] args) { try { Class<?> clazz = EnumSingleObject2.class; //通过反射获取类的私有构造方法 Constructor c = clazz.getDeclaredConstructor(null); //强制访问 c.setAccessible(true); Object obj1 = c.newInstance(); Object obj2 = c.newInstance(); //输出false System.out.println(obj1 == obj2); } catch (Exception e) { e.printStackTrace(); } }运行结果如下
protected Enum(String name, int ordinal) { this.name = name; this.ordinal = ordinal; }我们改变一下反射构建的方式
public static void main(String[] args) { try { Class<?> clazz = EnumSingleObject2.class; //通过反射获取类的私有构造方法 // Constructor c = clazz.getDeclaredConstructor(null); Constructor c = clazz.getDeclaredConstructor(String.class, int.class); //强制访问 c.setAccessible(true); Object obj1 = c.newInstance(); Object obj2 = c.newInstance(); //输出false System.out.println(obj1 == obj2); } catch (Exception e) { e.printStackTrace(); } }运行结果如下
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { ... 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()方法中做了强制性的判断,如果修饰符是Modifier.ENUM类型,则直接抛出异常。
/** * 容器式单例 */ public class ContainerSingleton { private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>(); public static Object getBean(String className) { synchronized (ioc) { if (!ioc.containsKey(className)) { Object obj = null; try { obj = Class.forName(className).newInstance(); ioc.put(className, obj); } catch (Exception e) { e.printStackTrace(); } return obj; } else { return ioc.get(className); } } } }容器式单例适合用于实例非常多的情况,Spring中就使用了该种单例模式。