当第一个线程调用getInstance()方法时,第二个线程也可以调用。当第一个线程执行到synchronized时会上锁,第二个线程就会变成MONITOR状态,出现阻塞。此时,阻塞并不是基于整个LazyDoubleCheckSingleton类的阻塞,而是在getInstance()方法内部的阻塞,只要逻辑不太复杂,对于调用者而言感觉不到。
4 看似完美的静态内部类单例写法双重检查锁单例写法虽然解决了线程安全问题和性能问题,但是只要用到synchronized关键字总是要上锁,对程序性能还是存在一定影响的。难道就真的没有更好的方案吗?当然有。我们可以从类初始化的角度考虑,看下面的代码,采用静态内部类的方式。
//这种形式兼顾饿汉式单例写法的内存浪费问题和synchronized的性能问题 //完美地屏蔽了这两个缺点 public class LazyStaticInnerClassSingleton { //使用LazyInnerClassGeneral的时候,默认会先初始化内部类 //如果没使用,则内部类是不加载的 private LazyStaticInnerClassSingleton(){ } //每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个方法不会被重写、重载 private static LazyStaticInnerClassSingleton getInstance(){ //在返回结果之前,一定会先加载内部类 return LazyHolder.INSTANCE; } //利用了Java本身的语法特点,默认不加载内部类 private static class LazyHolder{ private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton(); } }这种方式兼顾了饿汉式单例写法的内存浪费问题和synchronized的性能问题。内部类一定要在方法调用之前被初始化,巧妙地避免了线程安全问题。由于这种方式比较简单,就不再一步步调试。但是,“金无足赤,人无完人”,单例模式亦如此。这种写法就真的完美了吗?
5 还原反射破坏单例的事故现场我们来看一个事故现场。大家有没有发现,上面介绍的单例模式的构造方法除了加上private关键字,没有做任何处理。如果使用反射来调用其构造方法,再调用getInstance()方法,应该有两个不同的实例。现在来看客户端测试代码,以LazyStaticInnerClassSingleton为例。
public static void main(String[] args) { try{ //如果有人恶意用反射破坏 Class<?> clazz = LazyStaticInnerClassSingleton.class; //通过反射获取私有的构造方法 Constructor c = clazz.getDeclaredConstructor(null); //强制访问 c.setAccessible(true); //暴力初始化 Object o1 = c.newInstance(); //调用了两次构造方法,相当于“new”了两次,犯了原则性错误 Object o2 = c.newInstance(); System.out.println(o1 == o2); }catch (Exception e){ e.printStackTrace(); } }运行结果如下图所示。
显然,内存中创建了两个不同的实例。那怎么办呢?我们来做一次优化。我们在其构造方法中做一些限制,一旦出现多次重复创建,则直接抛出异常。优化后的代码如下。
public class LazyStaticInnerClassSingleton { //使用LazyInnerClassGeneral的时候,默认会先初始化内部类 //如果没使用,则内部类是不加载的 private LazyStaticInnerClassSingleton(){ if(LazyHolder.INSTANCE != null){ throw new RuntimeException("不允许创建多个实例"); } } //每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个方法不会被重写、重载 private static LazyStaticInnerClassSingleton getInstance(){ //在返回结果之前,一定会先加载内部类 return LazyHolder.INSTANCE; } //利用了Java本身的语法特点,默认不加载内部类 private static class LazyHolder{ private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton(); } }再运行客户端测试代码,结果如下图所示。