在之前的2篇博文漫谈模式之单例模式(多种实现方式的思考)和漫谈模式之单例模式(破坏和防护的思考),已经讲解了单例的多种实现方式以及单例在反射、序列化反序列化以及克隆场景下的破坏和防护思考。本文也迎来了漫谈单例模式的最后篇章,如何写一个通用的单例?
在开展讲解之前,先回答一下,为什么要搞一个通用的写法呢?
我们知道单例的写法有多种形式,每个人的风格不同。有一个通用的单例框架,可以有效保证风格的一致性。另外,鉴于开发人员的水平不一样,一个经过考验的通用单例模版,可以减少错误的引入。同时,也可以更好地去管理单例的使用。
当然,这并不是强制一定要这么做,使用D.C.L、内部Holder类或者枚举都是可以的。
接下来,我们在双重检查锁、CAS等实现单例的基础,看看通用单例的写法。
接口定义(并不是必须的)
定义一个获取实例接口,如:
抽象类
定义一个抽象的懒加载类。用于指定双重检查锁D.C.L的逻辑,同时指定一个create()方法用于具体子类创建对象使用。
使用示例
继承AbstractLazyInitializer,实现create()方法的内容,如:
然后通过如下方式完成调用即可。
AbstractLazyInitializer<SingletonExam> initializer = new SingletonExam();//获取实例initializer.get();
这样,一个D.C.L的通用写法就弄好了。
CAS我们通过原子类AtomicReference来做。
抽象类
定义一个抽象的懒加载类。维护一个可以原子更新的对象引用(初始值为null), 实现CAS的逻辑,同时指定一个create()方法用于具体子类创建对象使用。
使用示例
继承AbstractAtomicLazyInitializer,实现create()方法的内容,如:
然后通过如下方式完成调用即可。
AbstractAtomicLazyInitializer<SingletonExam> initializer = new SingletonAtomicExam();//获取实例initializer.get();
某次执行的结果如下:
可以看到,多线程在处理CAS操作过程中,可能有多个线程执行到create()去创建了对象,但是,因为只有一个线程更新原子引用成功并返回,其余创建的对象是创建后丢弃的。
那么问题来了, 是否有可能只让create()方法执行一次?
答案是肯定的。
我们可以再增加一个当前类的原子引用,做一道防护。在多线程环境下,只有设值成功的,才能去做create()操作。同时,CAS那部分,改成while循环,如下图所示:
CAS抽象类版本2
修改SingletonAtomicExam,继承自AbstractAtomicLazyInitializer2
然后,再执行一下测试代码,其结果如下:
我们可以看到只打印了一行************。也就是说create()方法确实只执行了一次。
上述代码在CAS不成功的时候,打印了do nothing,我们去掉一下。修改后的抽象类如下:
这样,CAS下只创建一次实例的抽象类也弄好了。
上述几种实现的思想,其实是之前看common-lang源码的时候摘记下来的,刚好引用到本文中来。common-lang的截图如下,有兴趣的读者可以去深入看看。
这是我比较喜欢的一种方式,也曾在项目中使用。
使用示例
编写一个实现Guava Supplier接口的示例
调用方式
Supplier<GuavaSupplierExam> initializer = Suppliers.memoize(GuavaSupplierExam::new);//获取实例 initializer.get()
测试一下:
Suppliers.memoize实现本质
其实现本质是使用2个变量来完成双重检测锁D.C.L
至此,多种适合单例的懒加载方式就实现就介绍完成了。
整个单例模式的漫谈系列三部曲也完结了。如果读者有其它好的学习内容,也请一起交流学习。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。