JavaExplore

          一切像霧像雨又像風(fēng)
          posts - 19, comments - 45, trackbacks - 0, articles - 0

          ???????? 今天去jdon,看了它的設(shè)計(jì)研究欄目,bang有幾篇評(píng)論單例模式的文章,聲稱“Singleton is evil”(見(jiàn)http://www.jdon.com/jive/article.jsp?forum=91&thread=17578),并且引用幾篇外文頁(yè)面佐證自己的觀點(diǎn),其中有一篇文章更是說(shuō),單例不僅不是一種模式,而是一種反模式。
          ??????? 下面我談?wù)勎覍?duì)單例模式的看法。逐一分析單例模式的陷阱,幫助大家正確使用單例模式。
          (1)?陷阱一:調(diào)用函數(shù)的性能瓶頸
          ??????? 在c++中,單例只有一種實(shí)現(xiàn)方式——LazySingleton, 實(shí)現(xiàn)如下(本文全部使用java代碼):

          public ? class ?LazySingleton {
          ????
          private ? static ?LazySingleton?m_instance = null ;
          ???? private?LazySingleton(){};
          ?????synchronized?public?static?LazySingleton?getInstance(){
          ????????
          if(m_instance==null)
          ????????????m_instance
          =new?LazySingleton();
          ????????
          return?m_instance;
          ????}

          }

          LazySingleton將對(duì)象的初始化推遲到調(diào)用的時(shí)候。并且為了防止多線程環(huán)境下產(chǎn)生多個(gè)實(shí)例,使用synchronized關(guān)鍵字保證函數(shù)getInstance調(diào)用的線程安全。synchronized關(guān)鍵字的存在保證了只會(huì)產(chǎn)生一個(gè)對(duì)象,但也成了多線程環(huán)境下的性能瓶頸。一個(gè)多線程的程序,到了這里卻要排隊(duì)等候成了一個(gè)單線程式的執(zhí)行流程,這在高并發(fā)環(huán)境下是不可容忍的。而c++中可以使用雙重檢查機(jī)制將這種性能問(wèn)題僅僅限制在第一次構(gòu)造對(duì)象的時(shí)候,而java中不可以使用雙重檢查機(jī)制。
          ????????但是java可以實(shí)現(xiàn)EagerSingleton,實(shí)現(xiàn)如下:

          public ? class ?EagerSingleton {
          ????
          private ? static ?EagerSingleton?m_instance = new ?EagerSingleton();
          ???? private?EagerSingleton(){};
          ???? public?static?agerSingleton?getInstance(){
          ????????
          return?m_instance;
          ????}

          }
          與LazySingleton相比,EagerSingleton將對(duì)象的初始化放到了類加載的時(shí)候。這樣就避免了synchronized關(guān)鍵字的性能瓶頸。
          (2)陷阱二:訪問(wèn)互斥共享資源
          ?????????EagerSingleton中訪問(wèn)互斥資源也要考慮線程安全問(wèn)題。下面看一個(gè)例子:
          public?class?EagerSingleton{
          ????
          private?static?EagerSingleton?m_instance=new?EagerSingleton();
          ????
          private?HashMap?map=new?HashMap();
          ???
          private?EagerSingleton(){};
          ???
          public?static?agerSingleton?getInstance(){
          ????????
          return?m_instance;
          ????}

          ???????
          public?void?refreshMap(Object key){
          ????????
          synchronized(map){
          ????????????
          if(!map.contains(key))
          ????????????????map.put(key,value);
          //value為此時(shí)的實(shí)時(shí)數(shù)據(jù)
          ????????}
          ?
          ????}

          }
          因?yàn)樵擃愂菃卫?,可能多線程并發(fā)訪問(wèn)map,map非線程安全,需要加線程安全關(guān)鍵字,否則就掉入了訪問(wèn)互斥資源的陷阱。
          (3)陷阱三:非法邏輯陷阱
          ??????? 這種情況一般是濫用單例模式造成的,下面考慮一種濫用單例的情況。下面的代碼的作用是getValueByName后,馬上printValue即完成操作流程。
          public?class?EagerSingleton{
          ????
          private?static?EagerSingleton?m_instance=new?EagerSingleton();
          ????
          private?String?value=null;
          ???
          private?EagerSingleton(){};
          ???
          public?static?agerSingleton?getInstance(){
          ????????
          return?m_instance;
          ????}

          ????
          synchronized?public?void?getValueByName(String?name){
          ????????value
          =getByNameFromDateBase(name);
          ????????
          ????}

          ????
          public?viod?printValue(){
          ????????System.out.println(
          this.vaue);
          ????}

          }

          該類含有一私有屬性value,在多線程環(huán)境下不能保證value值的合理邏輯,一線程getValueByName后,馬上printValue,也有可能value的值已經(jīng)被其他線程修改。這種情況就屬于單例模式的濫用,該類根本不適合做成單例。
          ??????? 消除非法邏輯的陷阱,可以通過(guò)將該類重構(gòu)為純粹的行為類完成。重構(gòu)后的代碼如下:

          public?class?EagerSingleton{
          ????
          private?static?EagerSingleton?m_instance=new?EagerSingleton();
          ???
          private?EagerSingleton(){};
          ???
          public?static?agerSingleton?getInstance(){
          ????????
          return?m_instance;
          ????}

          ????
          private?String?getValueByName(String?name){
          ????????
          return?getByNameFromDateBase(name);
          ????????
          ????}

          ????
          public?viod?printName(String?name){
          ????????String?value
          =getValueByName(String?name);
          ????????System.out.println(value);
          ????}

          }

          通過(guò)調(diào)用printName(String name)直接完成操作流程,將其中的私有屬性處理成過(guò)程式的參數(shù)傳遞,將該類修改成純粹的行為類。

          ??????? 含有私有屬性并且含有對(duì)它賦值操作的類并非都會(huì)調(diào)入該陷阱,構(gòu)造函數(shù)里進(jìn)行對(duì)私有屬性賦值不會(huì)引起非法邏輯,如下代碼

          public?class?EagerSingleton{
          ????
          private?static?EagerSingleton?m_instance=new?EagerSingleton();
          ????
          private?HashMap?map==new?HashMap();
          ????
          ??? private
          ?EagerSingleton(){
          ????????map.put(key,value);
          //value為此時(shí)的實(shí)時(shí)數(shù)據(jù)
          ????}

          ???
          public?static?agerSingleton?getInstance(){
          ????????
          return?m_instance;
          ????}

          }

          構(gòu)造函數(shù)里不必要加線程安全關(guān)鍵字也可以保證線程安全,因?yàn)轭惣虞d器是線程安全的,EagerSingleton只會(huì)在類加載的時(shí)候?qū)嵗淮?,這樣不會(huì)出現(xiàn)單例模式的線程不安全,也不會(huì)造成非法邏輯。
          (4)陷阱四:?jiǎn)卫葳宓膫鬟f
          ??????? 當(dāng)含有對(duì)象作為單例類的私有屬性時(shí),陷阱不僅會(huì)出現(xiàn)在該類本身,還會(huì)傳遞到私有對(duì)象所在的類中??慈缦麓a:

          public?class?EagerSingleton{
          ????
          private?static?EagerSingleton?m_instance=new?EagerSingleton();
          ????
          private?NewClass?newClass=nll;
          ???
          private?EagerSingleton(){
          ????????newClass
          =new?NewClass();
          ????}
          ;
          ???
          public?static?agerSingleton?getInstance(){
          ????????
          return?m_instance;
          ????}

          ???
          public?viod?printName(String?name){
          ????????String?value
          =newClass.operationByNameAndReturnValue(String?name);
          ????????System.out.println(value);
          ????}

          }
          乍一看,代碼中除了構(gòu)造函數(shù)對(duì)私有屬性進(jìn)行了初始化操作,其他地方?jīng)]有對(duì)私有屬性的賦值,不會(huì)引起非法邏輯陷阱。其實(shí)這個(gè)賦值操作可能隱含在newClass.operationByNameAndReturnValue(String name)操作,只有保證了NewClass的operationByNameAndReturnValue操作不會(huì)對(duì)它的私有屬性賦值操作,才能保證真正的合理邏輯。同樣,只有保證NewClass的operationByNameAndReturnValue操作沒(méi)有掉入訪問(wèn)互斥資源陷阱,才能真正保證EagerSingleton沒(méi)有掉入該陷阱。
          ??????? 消除該陷阱的方法:(1)類方法的名稱要合理,比如純粹的行為方法名:interprete,excute,operation之類的方法中就不該含有對(duì)私有屬性直接或者間接的賦值操作,每個(gè)方法的責(zé)任要明確。(2)單例類中盡量不要含有非單例類的實(shí)例作為私有屬性(容器類除外),一定要有類的實(shí)例作為私有屬性的時(shí)候,重新審視這個(gè)作為私有屬性的類,是不是也應(yīng)該設(shè)計(jì)成單例類;或者保證對(duì)它的初始化賦值限制在構(gòu)造函數(shù)內(nèi)。

          Feedback

          # re: 單例模式陷阱  回復(fù)  更多評(píng)論   

          2006-08-27 10:25 by lover
          精辟!很有收獲

          # re: 單例模式陷阱  回復(fù)  更多評(píng)論   

          2006-08-27 10:28 by lover
          你搞的不錯(cuò)!

          # re: 單例模式陷阱  回復(fù)  更多評(píng)論   

          2006-08-27 12:26 by cw
          大牛!受教了!

          # re: 單例模式陷阱  回復(fù)  更多評(píng)論   

          2006-08-28 16:27 by kalala
          咬住

          # re: 【原創(chuàng)】單例模式陷阱  回復(fù)  更多評(píng)論   

          2006-09-13 09:00 by 陳琪
          第一段
          public class LazySingleton {
          private static LazySingleton m_instance = null ;
          private LazySingleton(){};
          synchronized public static LazySingleton getInstance(){
          if(m_instance!=null)
          m_instance=new LazySingleton();
          return m_instance;
          }
          這個(gè)代碼是不是寫(xiě)錯(cuò)了?
          if(m_instance!=null)
          m_instance=new LazySingleton();
          是不是應(yīng)該為
          if(m_instance==null)
          m_instance=new LazySingleton();

          # re: 【原創(chuàng)】單例模式陷阱  回復(fù)  更多評(píng)論   

          2006-09-13 09:13 by 陳琪
          類加載器是線程安全的,受教了

          # re: 【原創(chuàng)】單例模式陷阱  回復(fù)  更多評(píng)論   

          2006-09-14 19:44 by JavaExplore
          @陳琪
          謝謝提醒 已經(jīng)改正

          # re: 【原創(chuàng)】單例模式陷阱  回復(fù)  更多評(píng)論   

          2006-11-21 22:30 by fish[匿名]
          頂下..

          # re: 【原創(chuàng)】單例模式陷阱  回復(fù)  更多評(píng)論   

          2006-11-30 16:59 by 路過(guò)
          調(diào)用函數(shù)的性能瓶頸<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
          鄙人以為這種瓶頸不屬于模式的問(wèn)題,多線程的方式里,都有這樣的問(wèn)題。要訪問(wèn)共享資源,就會(huì)有瓶頸,乃多線程的屬性,與模式無(wú)關(guān)。

          # re: 【原創(chuàng)】單例模式陷阱  回復(fù)  更多評(píng)論   

          2006-11-30 23:16 by JavaExplore [匿名]
          @路過(guò)
          你推翻了c++下經(jīng)典的雙重檢測(cè)機(jī)制

          # re: 【原創(chuàng)】單例模式陷阱  回復(fù)  更多評(píng)論   

          2007-08-10 17:03 by dreamstone
          其實(shí)可以用基于classloader的機(jī)制來(lái)實(shí)現(xiàn)。大部分情況下是可用的,不過(guò)如果基于多個(gè)classloader的程序則不行了。

          # re: 【原創(chuàng)】單例模式陷阱  回復(fù)  更多評(píng)論   

          2007-12-13 11:27 by 深秋小雨
          @JavaExplore [匿名]
          雙重成例檢測(cè)在Java中不成立,不能使用。閻博士說(shuō)得很清楚了。

          # re: 【原創(chuàng)】單例模式陷阱  回復(fù)  更多評(píng)論   

          2007-12-13 11:41 by 深秋小雨
          喔,我搞錯(cuò)情況了……不好意思

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 泗阳县| 富蕴县| 佳木斯市| 新津县| 全南县| 大安市| 遂宁市| 房产| 涿州市| 建瓯市| 郎溪县| 红安县| 芜湖市| 武乡县| 运城市| 静海县| 塔河县| 鄂托克旗| 兴化市| 皋兰县| 永登县| 新津县| 武义县| 岚皋县| 新丰县| 锡林浩特市| 资源县| 山西省| 鄂温| 永泰县| 石棉县| 高唐县| 凉城县| 高陵县| 绥滨县| 靖西县| 都安| 阳曲县| 农安县| 通辽市| 万荣县|