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

可以看到,在invokeReadResolve()方法中用反射调用了readResolveMethod方法。
通过JDK源码分析可以看出,虽然增加readResolve()方法返回实例解决了单例模式被破坏的问题,但是实际上单例对象被实例化了两次,只不过新创建的对象没有被返回而已。如果创建对象的动作发生频率加快,则意味着内存分配开销也会随之增大,难道真的就没办法从根本上解决问题吗?其实,枚举式单例写法也是能够避免这个问题发生的,因为它在类加载的时候就已经创建好了所有的对象。

8 还原克隆破坏单例的事故现场

假设有这样一个场景,如果克隆的目标对象恰好是单例对象,那会不会使单例对象被破坏呢?当然,我们在已知的情况下肯定不会这么干,但如果发生了意外怎么办?不妨来修改一下代码。

@Data public class ConcretePrototype implements Cloneable { private static ConcretePrototype instance = new ConcretePrototype(); private ConcretePrototype(){} public static ConcretePrototype getInstance(){ return instance; } @Override public ConcretePrototype clone() { try { return (ConcretePrototype)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } }

我们把构造方法私有化,并且提供getInstance()方法。编写客户端测试代码如下。

public static void main(String[] args) { //创建原型对象 ConcretePrototype prototype = ConcretePrototype.getInstance(); //复制原型对象 ConcretePrototype cloneType = prototype.clone(); System.out.println("原型对象和克隆对象比较:" + (prototype == cloneType)); }

运行结果如下图所示。

file

从运行结果来看,确实创建了两个不同的对象。实际上防止克隆破坏单例对象的解决思路非常简单,禁止克隆便可。要么我们的单例类不实现Cloneable接口,要么我们重写clone()方法,在clone()方法中返回单例对象即可,具体代码如下。

@Override public ConcretePrototype clone() { return instance; } 9 容器式单例写法解决大规模生产单例的问题

虽然枚举式单例写法更加优雅,但是也会存在一些问题。因为它在类加载时将所有的对象初始化都放在类内存中,这其实和饿汉式单例写法并无差异,不适合大量创建单例对象的场景。接下来看注册式单例模式的另一种写法,即容器式单例写法,创建ContainerSingleton类。

public class ContainerSingleton { private 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中的容器式单例写法的源码。

public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { /** Cache of unfinished FactoryBean instances: FactoryBean name --> BeanWrapper */ private final Map<String, BeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>(16); ... }

从上面代码来看,存储单例对象的容器其实就是一个Map。

9 附彩蛋:ThreadLocal线程单例

最后赠送大家一个彩蛋,线程单例实现ThreadLocal。ThreadLocal不能保证其创建的对象是全局唯一的,但能保证在单个线程中是唯一的,是线程安全的。下面来看代码。

public class ThreadLocalSingleton { private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>(){ @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } }; private ThreadLocalSingleton(){} public static ThreadLocalSingleton getInstance(){ return threadLocalInstance.get(); } }

客户端测试代码如下。

public static void main(String[] args) { System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); System.out.println(ThreadLocalSingleton.getInstance()); Thread t1 = new Thread(new ExectorThread()); Thread t2 = new Thread(new ExectorThread()); t1.start(); t2.start(); System.out.println("End"); }

运行结果如下图所示。

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

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