不知道哪位大神,提供了一种单例的写法,思考角度也很绝妙,从类初始化的角度考虑。这也是一种经典的单例实现方式,内部类单例实现。废话不多说,上代码:
/** * 内部类单例 */ public class InnerClassSingleton { private InnerClassSingleton(){} public static InnerClassSingleton getInstance(){ //在返回之前,一定要先加载内部类 return Innerclass.instance; } //利用了java的语法特点,默认不加载内部类 private static class Innerclass{ private static final InnerClassSingleton instance = new InnerClassSingleton(); } }
内部类的单例实现方式兼顾了饿汉式单例的内存浪费问题和synchronized加锁带来的性能损耗问题,并且内部类的实现方式只有在调用getInstance()方法的结果返回之前加载内部类,完成实例的初始化,巧妙的避免了线程安全问题,在不考虑序列化破坏单例和暴力反射破坏单例模式的情况下,这种实现方式堪称完美。接下来我们来看看暴力反射是如何破坏单例的:
public static void main(String[] args) { try{ Class<?> clazz = InnerClassSingleton.class; Constructor c = clazz.getDeclaredConstructor(null); c.setAccessible(true); //反射创建两个实例 Object o1 = c.newInstance(); Object o2 = c.newInstance(); System.out.println(o1==o2); //false }catch (Exception e){ e.printStackTrace(); } }
最终,在代码的最后一行一定是输出false,在内存中存在两个不同的实例,这样一来就违背了单例模式的初衷,有没有办法解决它呢?当然有,我们可以这优化内部类实现的单例模式:
/** * 内部类单例 */ public class InnerClassSingleton { private InnerClassSingleton(){} public static InnerClassSingleton getInstance(){ //加上判断,如果创建多个实例就抛出异常 if(Innerclass.instance!=null){ throw new RuntimeException("禁止创建多个实例!"); } //在返回之前,一定要先加载内部类 return Innerclass.instance; } //利用了java的语法特点,默认不加载内部类 private static class Innerclass{ private static final InnerClassSingleton instance = new InnerClassSingleton(); } }
这样似乎就大功告成啦!
当然除了以上几种常见的单例实现,还有一些单例实现方式:
/** * 枚举单例 */ public enum EnumSingleton { INSTANCE; private Object object; public Object getObject(){ return object; } public void setObject(Object object){ this.object = object; } public static Object getInstance(){ return INSTANCE; } }
枚举式单例是《Effective Java》书中比较推荐的一种单例实现方式,枚举的单例实现不仅是线程安全的,而且也能保证单例模式不会被破坏,可以说是一种既简单又安全的实现方式。
看到这种实现的名字可能有些小伙伴会感到陌生,我们大名鼎鼎的spring框架的单例就是基于此方式进行实现的。容器式单例的实现方式适用于需要大量创建单例对象的场景,便于管理。我们直接来看看spring的源码实现:
private final Map<String,Object> singletonObjects = new ConcurrentHashMap<>(256); protected Object getSingleton(String beanName, boolean allowEarlyReference) { //从缓存singletonObjects(实际上是一个map)中获取bean实例 Object singletonObject = this.singletonObjects.get(beanName); //如果为null,对缓存singletonObjects加锁,然后再从缓存中获取bean,如果继续为null,就创建一个bean。 if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) { Map var4 = this.singletonObjects; //双重判断加锁 synchronized(this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName); if (singletonFactory != null) { //通过 singletonFactory.getObject() 返回具体beanName对应的ObjectFactory来创建bean。 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } }
单例模式看似简单,其实里面还是还是有一些很深的学问值得研究的,比如我们的枚举式单例式怎么样防止恶意破坏的,这一点感兴趣的小伙伴可以自行研究一下,我这里就不贴出来了。单例模式的优点也有很多,比如:
1、单例模式在内存中只有一个实例,可以减少内存开销;
2、单例模式只提供一个全局的访问点,可以达到资源共享的目的。
当然缺点也有,比如:
1、单例模式没有接口,如果要扩展必须修改原来的代码,违背开闭原则;
2、单例模式的功能代码通常写在一个类中,如果设计不合理,很容易违背单一职责原则。
至此,单例模式的介绍就到此啦~
原文:https://www.cnblogs.com/chalice/p/14238366.html