前言
面试中…
A: 知道设计模式吗?
Q: 了解过一点..
A: 那,给我讲讲什么是单例模式?
Q: 啊? 尴尬…
是的,单例是面试的时候最喜欢问的,几乎提到设计模式,就会提到他。他是乍一看最简单的,但是他真的很简单吗?我并不这么感觉,应该这么说,单例模式本身简单,但当你需要用到这个模式的时候,你面对的情景已经不简单了。因为他往往伴随着线程、同步等问题一起出现,那么 开始吧!
单例模式
首先,来看一下他的定义:
单例模式(Single Pattern):确保一个类只有一个实例,并提供一个全局访问点。
由我自己的理解来概述一下单例模式,就是:不允许外部创建对象实例而是在类内部创建实例,外部通过get方法来获得此实例。这么说可能有点抽象,不便于理解,那么依然配合例子来说吧:
一个巨大的仓库
工厂模式中开的Pizza店十分成功,我们开了Pizza原料工厂,总店以及各种Pizza的分店。但是很快发现,我们需要一个仓库,一个巨大的仓库,能将购买的各种制作pizza的原料都存放在这个大仓库里。来吧!盖一个仓库!
1 | public class Warehouse{ |
仓库造完了,嘿嘿,看起来还不错呢?总感觉有点怪怪的?应该是我多心了?走!实地考察看看分店用这个仓库感觉如何!
谁的错?
你来到分店A,发现店门口2个人在争吵着,周围围着一群吃瓜群众…..
“怎么了!怎么了!”作为老板你走上前去,有些不快。走进发现,原来是你的采购员与A地区的工厂管理员。
-采购员:那天我买好的货物全都存进仓库了?
-A管理员:我去仓库提货的时候什么都没有!现在已经没原料了,工厂没原料加工,pizza店没有pizza卖!
-采购员:天哪!难道你是想说我在骗你?
-A管理员:我可没这么说,不过采购的费用确实是笔大数字,谁知道..
“够了!安静!”你打断了他们的争吵,具体情况大致了解了,你仔细想了想,眉头一皱,似乎发现了什么…
1 | //采购员 |
果然之前的预感是对的!这里出现了问题,在Purchaser中实例化了一个仓库Warehouse,同时在FactoryA中也实例化了一个Warehouse,这就是问题的所在。每个类在运行的时候内部的变量作用域仅在其内部,就算名字相同其实也仅仅是名字一样罢了,在内存中是不同的2个地址。换句话说!在运作的时候实例化了2个工厂!
紧急会议
你火急火燎的赶回总部,召开了会议,想解决这个问题。
会议上大家议论纷纷,突然有人提议”为什么不在运行的时候实例化仓库,并将其传递给工厂与采购员呢?这样就能保证他们使用的是一个仓库了!“说罢,大家纷纷向他投去了赞赏的目光,你低头沉思,这样似乎确实能解决目前的问题。
1 | //将 class Purchaser,class FactoryA中的构造方法改写一下 |
这样改写后,那么在使用的时候就需要传入参数,虽然解决的眼下的问题,但是如果有其他的采购员,其他的工厂,你都需要将仓库作为参数传递进去,这样简直太麻烦了!!!在你苦恼的时候,会议室的门突然被推开了,一个男子缓缓走进门,你看着他,但头脑中依然在思考着要不要使用刚刚的提议。
”我能解决你的麻烦!“那人说道。 众人交头接耳,议论着什么。”你是谁!“作为老板,你开了口。
男子微微一笑”SinglePattern,在下单例模式!“
突然的帮手
那位陌生人说出了一套令人无法拒绝的方案:将Warehouse类的构造函数写为private的,这样在外部就没有人可以使用Warehouse的构造器,同时,在Warehouse类内部写一个静态方法,由这个静态方法来返回Warehouse的实例。由于这个是静态方法,那么在全局都可以使用,当调用这个方法时,会判断如果已经有了实例,就会直接返回,这样就不会重复创建对象了。
1 | public class Warehouse{ |
到这里,单例模式大展身手,完美的解决了之前的问题,你的巨大仓库得以能够正常工作。似乎就该这么画上完美的句点了。
多线程
之前的一节主要引出了单例模式并且写了一个例子,我觉得与其让我用刻板的术语来说单例的好处,不如大家实际看起来直观,可以一步步的看到遇到的问题,解决的思路,单例模式能做到的事情显然意见了。这一节会针对单例模式在多线程时所遇到的一些问题来说。好的、继续吧!
(我突然发现编故事也挺好玩的。。。)
两个采购员
经过了之前的问题,大家现在又回到了工作中,你看着你的采购员每天辛辛苦苦的为这个”巨大”仓库补货,感觉太辛苦他了,于是你打算再招一个采购员,当他们一起合作。一开始他们合作的也比较顺利,直到有一天…
你新推出了一种pizza,他是那么的火爆,库存的那些原料在短短的1周就全部用完了!你十分高兴,一边看着财报,一边对你的采购员说,”你们2个,在下周前给我去采购共计10W份pizza的原料回来,回来就涨工资!“大家都十分兴奋,努力的工作去了。
1 | public class Simulator{ |
一周过后,你来到你的巨大仓库,惊讶的发现,眼前有着2个仓库!!!为什么!为什么!又出现了2个仓库!!!
两个仓库
这回你冷静了很多,毕竟是经历过大场面的人了,你回想起线程的种种相关知识:
线程创建后就会自行开始运行,不再受控制,在上面写的方法中,由Warehouse.getInstance获得实例,第一次执行的时候是null,创建一个新的实例返回;第二次执行的时候不为null,应该返回上一个实例。但是,由于线程是同时执行的,所以第一次执行的时候有可能的情况是:A,B同时执行,当然这时候实例为null,所以他们分别创建了2个仓库实例。对于他们来说,确实是没有实例的情况下创建的!
这时候单例模式又出现了,他只和你说了几句话,你便松了一口气,他说了什么?synchronize!
synchronize可以让其包括的代码块具有原子性,即在一个对象在执行这段代码中时,其他对象无法执行这段代码,只有当之前的人执行完毕。
1 | public class Warehouse{ |
效率问题
上面对于多线程时可能会遇到多次实例化的情况做出了解决,现在想一想,这样好吗?不是很好。。。
是不是很惊讶!我当时写到这里的时候觉得应该已经perfect了,但是有一个问题被疏忽了:效率。来看看上面的代码 由于synchronized(){}的存在,所以每次在执行到这里的时候,都要去等前一个人将其执行完才能执行?有必要吗?确实,在Warehouse对象是null的时候,是需要的,因为要防止创建多个实例对象;但是当他不为null的时候,就不必要了。为什么?因为在已经有实例的情况下,不管是谁需要实例,都会返回当前已经有的那个实例,也就没必要去排队等待了。
为了解决这个效率问题,很多方式也被提出:
饿汉式
1 | //使用静态变量,在运行前就直接创建实例 |
双重检查锁
1 | // 双重检查锁,首先检查实例是不是已经创建了,如果未创建,才进行同步,这样只有第一次会同步 |
内部静态类
1 | //这种好一些,既实现了线程安全,又避免了同步带来的性能影响 |
结语
终于写完了,编故事好累,中间改了很久,也算是编完了吧…单例大概也算是说完了,就觉得还少了点什么。什么?你说为什么没有类图?啊,看了这么多,自己想想吧!单例模式的类图只有一个类哦,就是他自己!里面有些什么呢?再翻翻上面的东西思考一下吧。。。
第二篇算是写完了,接下来。。什么时候更新呢、、真的写这个好累啊。估计2周后会有下一篇吧。是什么模式呢?我也没想好。
最近又开了个坑,想想还有点小激动呢!我要做游戏拉!!!啊哈哈哈哈!!!