这9个单例被破坏的事故现场,你遇到过几个? 评论区见 (3)

file

当第一个线程调用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(); } }

运行结果如下图所示。

file

显然,内存中创建了两个不同的实例。那怎么办呢?我们来做一次优化。我们在其构造方法中做一些限制,一旦出现多次重复创建,则直接抛出异常。优化后的代码如下。

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(); } }

再运行客户端测试代码,结果如下图所示。

file

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwjzzw.html