“你知道茴香豆的‘茴’字有几种写法吗?”
纠结单例模式有几种写法有用吗?有点用,口试中常常选择个中一种或几种写法作为话头,观察设计模式和coding style的同时,还很容易扩展到其他问题。这里讲授几种猴哥常用的写法,但切忌生搬硬套,去记“茴香豆的写法”。编程最大的兴趣在于“know everything, control everything”。
概略可分为4类,下面别离先容他们的根基形式、变种及特点。
饱汉模式
饱汉是变种最多的单例模式。我们从饱汉出发,通过其变种逐渐相识实现单例模式时需要存眷的问题。
基本的饱汉
饱汉,即已经吃饱,不着急再吃,软件开发,饿的时候再吃。所以他就先不初始化单例,等级一次利用的时候再初始化,即“懒加载”。
// 饱汉 // UnThreadSafe public class Singleton1 { private static Singleton1 singleton = null; private Singleton1() { } public static Singleton1 getInstance() { if (singleton == null) { singleton = new Singleton1(); } return singleton; }
饱汉模式的焦点就是懒加载。长处是更启动速度快、节减资源,一直到实例被第一次会见,才需要初始化单例;小弊端是写起来贫苦,大弊端是线程不安详,if语句存在竞态条件。
写起来贫苦不是大问题,可读性好啊。因此,单线程情况下,基本饱汉是猴哥最喜欢的写法。但多线程情况下,基本饱汉就彻底不行用了。下面的几种变种都在试图办理基本饱汉线程不安详的问题。
饱汉 – 变种 1
最粗暴的犯罪是用synchronized要害字修饰getInstance()要领,这样能到达绝对的线程安详。
// 饱汉 // ThreadSafe public class Singleton1_1 { private static Singleton1_1 singleton = null; private Singleton1_1() { } public synchronized static Singleton1_1 getInstance() { if (singleton == null) { singleton = new Singleton1_1(); } return singleton; } }
变种1的长处是写起来简朴,且绝对线程安详;弊端是并发机能极差,事实上完全退化到了串行。单例只需要初始化一次,但就算初始化今后,synchronized的锁也无法避开,从而getInstance()完全酿成了串行操纵。机能不敏感的场景发起利用。
饱汉 – 变种 2
变种2是“污名昭著”的DCL 1.0。
针对变种1中单例初始化后锁仍然无法避开的问题,劳务派遣管理系统,变种2在变种1的外层又套了一层check,加上synchronized内层的check,即所谓“双重查抄锁”(Double Check Lock,简称DCL)。
// 饱汉 // UnThreadSafe public class Singleton1_2 { private static Singleton1_2 singleton = null; private Singleton1_2() { } public static Singleton1_2 getInstance() { // may get half object if (singleton == null) { synchronized (Singleton1_2.class) { if (singleton == null) { singleton = new Singleton1_2(); } } } return singleton; } }
变种2的焦点是DCL,看起来变种2好像已经到达了抱负的结果:懒加载+线程安详。惋惜的是,正如注释中所说,DCL仍然是线程不安详的,由于指令重排序,你大概会获得“半个工具”。具体在看完变种3后,可参考猴子之前的一篇文章,这里不再赘述。
参考:volatile要害字的浸染、道理
饱汉 – 变种 3
变种3专门针对变种2,可谓DCL 2.0。
针对变种3的“半个工具”问题,变种3在instance上增加了volatile要害字,道理见上述参考。
// 饱汉 // ThreadSafe public class Singleton1_3 { private static volatile Singleton1_3 singleton = null; private Singleton1_3() { } public static Singleton1_3 getInstance() { if (singleton == null) { synchronized (Singleton1_3.class) { // must be a complete instance if (singleton == null) { singleton = new Singleton1_3(); } } } return singleton; } }
多线程情况下,变种3更合用于机能敏感的场景。但后头我们将相识到,就算是线程安详的,尚有一些步伐能粉碎单例。
饿汉模式
与饱汉相对,饿汉很饿,只想着尽早吃到。所以他就在最早的机缘,即类加载时初始化单例,劳务派遣管理系统,今后会见时直接返回即可。
// 饿汉 // ThreadSafe public class Singleton2 { private static final Singleton2 singleton = new Singleton2(); private Singleton2() { } public static Singleton2 getInstance() { return singleton; } }
饿汉的长处是天生的线程安详(得益于类加载机制),写起来超等简朴,利用时没有延迟;弊端是有大概造成资源挥霍(假如类加载后就一直不利用单例的话)。
值得留意的时,单线程情况下,饿汉与饱汉在机能上没什么不同;但多线程情况下,由于饱汉需要加锁,饿汉的机能反而更优。
Holder模式