设计模式(003) 单例模式[上] 单身懒汉
什么是单例模式(What)?
GOF:“保证一个类仅有一个实例,并且提供一个访问它的全局访问点”。
“嗯...,GOF通常一言九鼎,单例就是这样子的。” -- OO先生边思考边说。
YSJIAN :“等等,我插一句,保证应用中一个类最多只有一个实例存在,并提供一个全局访问点访问它”。
只见OO先生会心一笑。
为什么用单例模式(Why)?
从What中貌似一目了然了,GOF和YSJIAN说的都是为了控制类的实例个数,表面上看YSJIAN的插话只是在重复GOF的描述,喜欢“咬文嚼字”的OO先生发现了这其中的玄机。首先回答一个问题:由谁类控制类的实例个数呢?
有人说:单利要多见简单有多简单,public static final INSTANCE = new Instance();你要用我的实例,Instance.INSTANCE拿走即可。的确,你发了一条广播出去,接收到的人会乐意按照你说的方法去使用,而不知道的人呢,更有new党,一上来就来一个new Instance(),然后到处开花,悲剧了。
怎么办?让Instance自己来控制实例的个数,而不是交给使用者,正所谓将邪恶扼杀于源头,可避免是是非非。当然这就需要Instance多干点活了。也没关系,一次搞定,服务持久,何乐而不为。
单例模式在哪里用(Where)?
我要说单利用在需要它的地方就有点zuo了。这么讲吧,如果这个类只需要一个实例即可满足我们的业务需求,或者它的实例一旦多余一个会出问题。比如说中国,China这个类如果不是单例就有点不合适了。一个工厂的中央炼钢炉,涉及到钢水,水,温度,气压这些危险地东西,一旦某个数值有差错就可能造成生产事故,这个时候炼钢炉存在多个实例就是万万不允许的,或者是有多个线程去操作非同步的它也是不合理的。
如何运用单例模式(How)?
单例,毫不夸张地说,是个开发者都能随手画出一个,赶紧上图:
public class LazyUnsafeSingleton { private volatile static LazyUnsafeSingleton instance; private LazyUnsafeSingleton() { } public static LazyUnsafeSingleton getInstance() { if (null == instance) { instance = new LazyUnsafeSingleton(); } return instance; } }
貌似这个就可能出现问题,当多线程操作的时候,难保它能搞出几个实例来,于是YSJIAN加了几行代码,使用了第一招双重校验加同步:
public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton instance; private LazyDoubleCheckSingleton() { Logger.getGlobal().info("LazyDoubleCheckSingleton.LazyDoubleCheckSingleton()"); } public static LazyDoubleCheckSingleton getInstance() { if (null == instance) { synchronized (LazyDoubleCheckSingleton.class) { if (null == instance) { instance = new LazyDoubleCheckSingleton(); } } } return instance; } }
OO先生若有所思头地看着,边点头边吐出了一句:”内部类实例持有者可以让代码再简洁点...”,几分钟后,JSJIAN又上一副新图:
public class LazyHolderSingleton { private static class InstanceHolder { private static final LazyHolderSingleton INSTANCE = new LazyHolderSingleton(); } private LazyHolderSingleton() { Logger.getGlobal().info("LazyHolderSingleton.LazyHOlderSingleton()"); } public static LazyHolderSingleton getInstance() { return InstanceHolder.INSTANCE; } }
细心的你已经发现了什么了?这几个单例,类名都是以Lazy为前缀。不用查找翻译啦,Lazy就是说这几单例很懒,没事就在那睡懒觉,什么时候叫他了(invoke:调用),他就醒了,并开始为你效劳。
第一种,线程不安全;第二种,解决了线程安全问题。第三种,JVM类加载机制决定了它的线程安全。第四种…有第四种吗?这个可以有,也可以没有,不卖关子了,肯能你已经想到了,且看图:
public class LazyMethodSynSingleton { private static LazyMethodSynSingleton instance; private LazyMethodSynSingleton() { Logger.getGlobal().info("LazyMethodSynSingleton.LazyMethodSynSingleton()"); } public static synchronized LazyMethodSynSingleton getInstance() { if (null == instance) { instance = new LazyMethodSynSingleton(); } return instance; } }
不出意外,这个也是Lazy的,无非就是将双重校验加同步中的同步加到方法上了,我称之为方法同步。仔细揣摩两者的区别,不难发现:
双重校验加同步:只有Instace为null的时候加锁,而且这把锁几乎不用去管它。
方法同步:不论Instance是否为null,先加把锁再说,而且每次调用都会加锁。
对比完两者的区别,我想之前使用方法同步的同学就要赶紧转移阵地了,如果执意不想转的同学,试问一下:一个99%的情况下不需要加锁的,你去给加了把锁,是不是浪费很多不必要的时间,不合适,对吧,太伤感情,JVM会生气的!
到这里,懒汉式单例经典实现就是上面的四种了,第一种,因为无意识而没有安全感,第四种因为对危险充满恐惧,徒耗精力。选项貌似一下子砍了50%,至于使用哪种,随心,随性就好,我遵循代码简洁之道(此处并不是特指Robert C. Martin的Clean Code),选择了内部类实例持有者。
懒汉子即将返璞归真,退出舞台,且看下个回合饿汉子如何搔首弄姿。
PS : OO先生,YSJIAN,GOF,等加粗斜体的表示人名,详细内容欢迎回顾《开启设计之旅》。亲爱的同仁,若有疑问,欢迎留言交流。
其他参考: