??xml version="1.0" encoding="utf-8" standalone="yes"?>
q个写法的发明者是Google公司的Bob lee?br>他还写了个轻量IoC容器Q据说不要配制文Ӟ性能比spring?00倍。感?00倍有些夸张,不过可以看看?br>
最后的部分Q?
看完本文Q如果你对visitor模式有更多的兴趣Q想了解更多L如下几篇文章?br>1,静态分z,动态分z,多分z,单分z?-------------- visitor模式准备
2,讉K差异cd的集合类 ------------------------ visitor模式入门
3,visitor模式理论及学术概?------------Q?---- visitor模式q一?font color=#7297ce>(本文Q?/font>
4Q?a href="http://www.aygfsteel.com/dreamstone/archive/2006/12/18/88623.html">重蝲overloading和覆写overriding哪个更早执行-- visitor帮助?/font>
虽然排列序?,2,3,4 但是我个人徏议的学习方式?,1,3,4因ؓq个序更方便一般h理解
看完本文Q如果你对visitor模式有更多的兴趣Q想了解更多L如下几篇文章?br>1,静态分z,动态分z,多分z,单分z?-------------- visitor模式准备(本文Q?/font>
2,讉K差异cd的集合类 ------------------------ visitor模式入门
3,visitor模式理论及学术概?------------Q?---- visitor模式q一?br>4Q?a href="http://www.aygfsteel.com/dreamstone/archive/2006/12/18/88623.html">重蝲overloading和覆写overriding哪个更早执行-- visitor帮助?/a>
虽然排列序?,2,3,4但是我个人徏议的学习方式?,1,3,4因ؓq个序更方便一般h理解
q种讉K的特Ҏ集合cM的对象是同一cd象PersonQ他们拥有功能的Ҏrun,我们调用的恰好是q个共同的方法?br>在大部䆾的情况下Q这个是可以的,但在一些复杂的情况Q如被访问者的l承l构复杂Q被讉K者的q不是同一cd象,
也就是说不是l承自同一个根cR方法名也ƈ不相同。例如Java GUI中的事g是一个例子?br>例如q样的问题,有如下类和方?
c:PA ,ҎQrunPA();
c:PB ,ҎQrunPB();
c:PC ,ҎQrunPC();
c:PD ,ҎQrunPD();
c:PE ,ҎQrunPE();
有一个集合类List
List list = new ArrayList();
list.add(new PA());
list.add(new PB());
list.add(new PC());
list.add(new PD());
list.add(new PE());
....
?解决
要求能访问到每个cȝ对应的方法。我们第一反应应该是这L?br>
三:新问题及分析解决
当数目变多的时候,l护if else是个费力气的事情Q?br>仔细分析if,else做的工作Q首先判断类型,然後Ҏcd执行相应的函?br>如何才能解决q两个问题呢Q首先想到的是java的多态,多态就是根据参数执行相应的内容Q?br>能很Ҏ的解决第二个问题Q我们可以写q样一个类Q?br>
q样只要调用runҎQ传入对应的参数p执行了?br>q有一个问题就是判断类型。由于重?overloading)是静态多分配Qjava语言本n是支?静态多分配"的?br>关于q个概念Lq里)所以造成重蝲只根据传入对象的定义cdQ而不是实际的cdQ所以必d传入前就定cdQ?br>q可是个隄问题,因ؓ在容器中对象全是ObjectQ出来后要是判断是什么类型必ȝ
if (xx instanceof xxx)q种ҎQ如果用q种Ҏ启不是又回到了原点,有没有什么更好的办法呢?
我们知到Javaq有另外一个特点,覆写(overriding)Q而覆写是"动态单分配"的(关于q个概念见这?Q?br>那如何利用这个来实现呢?看下边这个方?
我们让上边的一些类PA PB PC PD PE都实C个接口P,加入一个方?accept();
首先执行的是"把自׃?"Q在q里׃Java的特性,实际执行的是子类的accept(),也就是实际类的accept
然後?把自׃?"Q在q里再次把this传入Q就明确cdQok我们巧妙的利用overriding解决了这个问?br>其实归纳一下第二部分,一个关键点?自己认识自己"Q是不是很可W?br>其实在计计技术领域的很多技术上看v来很高深的东西,其实是现有C会中h的生zL式的一U映?br>而且q种方式是简单的不能再简单的方式。上边的全部q程基本上是一个简单的visitor模式实现,visitor模式
已经是设计模式中比较复杂的模式了Q但其实原理单到你想W。看看下边这个比M怽的理解会更深刅R?/p>
四:一个帮助理解的比喻Q?br>题目Q指挥工人工?br>条gQ你?0个全能工人,10L同工作?br>需求:做完工作
实现Q大喊一声所有hd?/p>
条g变了Q工Z是全能,但是工作相同Qok问题不大
条g再变,工作不是相同Q但工h是全能,ok问题不大
以上三种情况在现实生zM是很发生得Q最多的情况是这P
10个工人,每h会做一U工作,10样工作。你又一份名单Collection)写着谁做什么。但你不认识M?br>q个时候你怎么指挥呢,Ҏ一Q?br>你可以一个个的叫工hQ然後问他们名字Q认识他们,查名单,告诉他们做什么工作?br>你可以直接叫Z们名字,告诉他们q什么,不需要知C是谁?br>看v来很单。但如果你要指挥10万h?Q而且人员是流动的Q每天的Z同,你每天拿C张文?br>其实很简单,最常用的做法是Q你把这份名单脓在墙上,然後大喊一壎ͼ所有h按照ȝQ按照自q分配情况d?br>q里利用的关键点?所有工p识自?Q你不能苛求每个工h会做所有工作,不能苛求所有工作相同,但你
能要求所有工人都认识自己?/p>
再想x们开始的E序,每个工h对应着PA PB PC PD PE....
所有的工h都工hP
每个工h会做的东西不一样runPA runPB runPC
你有一份名单VisitorQ重载)记录着谁做什么工作?/p>
看完上边q些Q你是不是会产生如下的问题:
问题Qؓ什么不把这些方法的Ҏ名做成一LQ那可以解决了?br>例如Q我们每个PA ,PB ,PC都加入一个run ҎQ然後run内部再调用自己对应的runPx()Ҏ?br>{案Q有些时候从不同的角度考虑Q或者因为实现的复杂度早成很隄一Ҏ名?br>例如上边指挥人工作的例子的例子,其实runҎ是大叫一声去工作Q因为每个工人只会做一U工作,所以能?br>但我们不能要求所有h只能会做一U事情,q个要求很愚蠢。所以如果每个工Zq两U或者多U工作呢,
也就是我PA 有runPA() walkPA(){等ҎQ?PB有runPB() climbPB(){等。。?br>q个时候按照名单做事才是最好的办法?/p>
五:作者的?br>所以说模式中很多复杂的东西Q在现实中其实是很基本的东西Q多多代入代帮助理解模式?/p>
看完本文Q如果你对visitor模式有更多的兴趣Q想了解更多L如下几篇文章?br>1,静态分z,动态分z,多分z,单分z?-------------- visitor模式准备
2,讉K差异cd的集合类 ------------------------ visitor模式入门Q本文)
3,visitor模式理论及学术概?------------Q?---- visitor模式深入
4Q?a href="http://www.aygfsteel.com/dreamstone/archive/2006/12/18/88625.html">重蝲overloading和覆写overriding哪个更早执行-- visitor帮助?
虽然排列序?,2,3,4 但是我个人徏议的学习方式?,1,3,4因ؓq个序更方便一般h理解
Ҏ依赖倒置Q控制反转,依赖注入?
Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q?br>1Q依赖倒置Q?br>先说明什么是"正置"
正置是Q当你设计一些类库的时候,考虑到用L需要定义一些接口,用户的应用程序依赖这些接?/p>
Ҏ"正置"?倒置"
正置Q应用依赖接口,接口q库设计者定?br>优点Q类库实C自由度大Q实现容易?br>~点Q类库设计复杂,要预应用的需求,同时有可能不W合应用的需?/p>
倒置Q应用定义接口,cd设计者实现接口,按照接口实现cd
优点Q类库设计不再需要预,肯定W合应用需?br>~点Q类库有可能难以实现
单来_正置和倒置的区别在于,谁来制定标准。现实中因ؓ需求的定义是客户决定的Q所以倒置?br>适合E序的设计。从需求出发,到底层实现?/p>
使用目的Q便于应用按照需求设?br>x?接口的设?br>Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q?br>2Q控制反转:
正常的控Ӟ
没有一定的控制程Q下一个流E由上一个流E决定,实现应用需要自己控制流E?br>控制反{ Q?br> 抽象出固定的程Q实现应用就是填充流E中的一个个炏V?br> 所有的框架都是Z控制反{的。Windows GUI开发也多是q样?br>目的Q高度抽象,设计框架
x点:程的设?br>Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q?br>3,依赖注入
原始的情况:
应用自己new c,或者用工厂模式创建类
依赖注入Q?br> 容器创徏c,通过构造函敎ͼsetterҎQ接口等ҎQ运行时"注入"到应用中
实现Q?br>Spring setterҎ
webwork IOC 实现接口的方?br>pico 构造函数的Ҏ
目的Q完全分d?br>x点:构造对?/p>
Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q?br>关系Q?br>"控制反{"?依赖倒置"的一U?"依赖注入"是在"控制反{"的基上,让容器来完成"注入"q程?br>Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q?br>最需要依赖注入的情况Q?br>"调用?和多?被调用?分别开发,分别打包分发Q实施者根据实际情况决定用哪?被调用?被?br>做成可配制的Q这U情冉|l常需要的。这个时候调用者内不能出现q样的代?
interface inter = new interfaceimplA();cM的语句。因Z用的可能是interfaceimplB
Z避免q样语句的出玎ͼ我们直接使用接口Q而对象的构造推q,让容器根据实施者的配制来构造,q注?br>正确的位|?/p>
依赖倒置、控制反转和依赖注入辨析
在《道法自然——面向对象实跉|南》一书中Q我们采用了一个对立统一的辩证关pL说明“模板Ҏ”模式—?“正向依赖 vs. 依赖倒置”Q参见:《道法自然》第15章[王咏武, 王咏?2004]Q。这U把“好莱?#8221;原则?“依赖倒置”原则{量齐观的看法其实来自于轻量U容器PicoContainer主页上的一D话Q?br> “控制反{QInversion of ControlQ的一个著名的同义原则是由Robert C. Martin提出的依赖倒置原则QDependency Inversion PrincipleQ,它的另一个昵U是好莱坞原则(Hollywood PrincipleQ不要调用我Q让我来调用你)”[PicoContainer 2004]?br> 和网友们在CSDN Blog上进行了深入的讨论后Q我又把q些概念重新梳理了一下。我发现Q这几个概念虽然在思\和动机等宏观层面上是l一的,但在具体的应用层面还是存在着许多很微妙的差别。本文通过几个单的例子对依赖倒置QDependency Inversion PrincipleQ、控制反转(Inversion of ControlQ、依赖注入(Dependency InjectionQ等概念q行了更为深入的辨析Q也是对于《道法自然》正文内容的一个补充吧?
依赖和耦合QDependency and CouplingQ?nbsp;
Rational Rose的帮助文档上是这样定?#8220;依赖”关系的:“依赖描述了两个模型元素之间的关系Q如果被依赖的模型元素发生变化就会媄响到另一个模型元素。典型的Q在cd上,依赖关系表明客户cȝ操作会调用服务器cȝ操作?#8221;
Martin Fowler在《Reducing Coupling》一文中q样描述耦合Q?#8220;如果改变E序的一个模块要求另一个模块同时发生变化,p两个模块发生了耦合?#8221; [Fowler 2001]
从上面的定义可以看出Q如果模块A调用模块B提供的方法,或访问模块B中的某些数据成员Q当Ӟ在面向对象开发中一般不提倡这样做Q,我们p为模块A依赖于模块BQ模块A和模块B之间发生了耦合?br> 那么Q依赖对于我们来说究竟是好事q是坏事呢?
׃人类的理解力有限Q大多数人难以理解和把握q于复杂的系l。把软gpȝ划分成多个模块,可以有效控制模块的复杂度Q每个模块都易于理解和l护。但在这U情况下Q模块之间就必须以某U方式交换信息,也就是必然要发生某种耦合关系。如果某个模块和其它模块没有M兌Q哪怕只是潜在的或隐含的依赖关系Q,我们几乎可以断定,该模块不属于此Y件系l,应该从系l中剔除。如果所有模块之间都没有M耦合关系Q其l果必然是:整个软g不过是多个互不相q的pȝ的简单堆U,Ҏ个系l而言Q所有功能还是要在一个模块中实现Q这{于没有做Q何模块的分解?br> 因此Q模块之间必定会有这h那样的依赖关p,永远不要qL消除所有依赖。但是,q强的耦合关系Q如一个模块的变化会造成一个或多个其他模块也同时发生变化的依赖关系Q会对Y件系l的质量造成很大的危実뀂特别是当需求发生变化时Q代码的l护成本非帔R。所以,我们必须惛_办法来控制和消解不必要的耦合Q特别是那种会导致其它模块发生不可控变化的依赖关pR依赖倒置、控制反转、依赖注入等原则是Z在和依赖关系q行艰苦卓绝的斗争过E中不断产生和发展v来的?br>
接口和实现分?nbsp;
把接口和实现分开是h们试图控制依赖关pȝW一个尝试,?1是Robert C. Martin在《依赖倒置》[Martin 1996]一文中所丄W一个例子。其中,ReadKeyboard()和WritePrinter()为函数库中的两个函数Q应用程序@环调用这两个函数Q以便把用户键入的字W拷贝到打印出?br>
依赖倒置QDependency Inversion PrincipleQ?nbsp;
可以看出Q上面讨论的单分L口的Ҏ对于依赖关系的消解作用非常有限。Java语言提供了纯_的接口c,q种接口cM包括M实现代码Q可以更好地隔离两个模块。C++语言中虽然没有定义这U纯_的接口c,但所有成员函数都是纯虚函数的抽象cM不包含Q何实C码,可以起到cM于Java接口cȝ作用。ؓ了和上一节中提到的简单接口相区别Q本文后面将把基于Java 接口cLC++抽象cd义的接UCؓ抽象接口。依赖倒置原则是建立在抽象接口的基础上的。Robert Martinq样描述依赖倒置原则[Martin 1996]Q?br> A. 上层模块不应该依赖于下层模块Q它们共同依赖于一个抽象?br> B. 抽象不能依赖于具象,兯依赖于抽象?br> 其含义是Qؓ了消解两个模块间的依赖关p,应该在两个模块之间定义一个抽象接口,上层模块调用抽象接口定义的函敎ͼ下层模块实现该接口。如?3所C,对于上一节的例子Q我们可以定义两个抽象类Reader和Writer作ؓ抽象接口Q其中的Read()和Write()函数都是U虚函数Q而具体的KeyboardReader和PrinterWritercdCq些接口。当应用E序调用Read()和Write()函数Ӟ׃多态性机制的作用Q实际调用的是具体的KeyboardReader和PrinterWritercM的实现。因此,抽象接口隔离了应用程序和cd中的具体c,使它们之间没有直接的耦合关系Q可以独立地扩展或重用。例如,我们可以用类似的Ҏ实现FileReader或DiskWriterc,应用E序既可以根据需要选择从键盘或文g输入Q也可以选择向打印机或磁盘输出,甚至同时完成多种不同的输入、输ZQ务。由此可以ȝ出,q种通过抽象接口消解应用E序和类库之间依赖关pȝ做法h以下特点Q?br> 1. 应用E序调用cd的抽象接口,依赖于类库的抽象接口Q具体的实现cL生自cd的抽象接口,也依赖于cd的抽象接口?br> 2. 应用E序和具体的cd实现完全独立Q相互之间没有直接的依赖关系Q只要保持接口类的稳定,应用E序和类库的具体实现都可以独立地发生变化?br> 3. cd完全可以独立重用Q应用程序可以和M一个实C相同抽象接口的类库协同工作?
控制反{QInversion of ControlQ?nbsp;
前面描述的是应用E序和类库之间的依赖关系。如果我们开发的不是cdQ而是框架pȝQ依赖关pd会更强烈一炏V那么,该如何消解框架和应用E序之间的依赖关pdQ?br> 《道法自然》第5章描qC框架和类库之间的区别Q?br> “框架和类库最重要的区别是Q框架是一?#8216;半成?#8217;的应用程序,而类库只包含一pd可被应用E序调用的类?br> “cdl用h供了一pd可复用的c,q些cȝ设计都符合面向对象原则和模式。用户用时Q可以创些类的实例,或从q些cMl承出新的派生类Q然后调用类中相应的功能。在q一q程中,cdL被动地响应用L调用h?br> “框架则会为某一特定目的实现一个基本的、可执行的架构。框架中已经包含了应用程序从启动到运行的主要程Q流E中那些无法预先定的步骤留l用h实现。程序运行时Q框架系l自动调用用户实现的功能lg。这Ӟ框架pȝ的行为是d的?br> “我们可以_cd是死的,而框架是zȝ。应用程序通过调用cd来完成特定的功能Q而框架则通过调用应用E序来实现整个操作流E。框架是控制倒置原则的完体现?#8221;
框架pȝ的一个最好的例子是囑Ş用户界面QGUIQ系l。一个简单的Q用面向过E的设计Ҏ开发的GUIpȝ如图 5所C?
依赖注入QDependency InjectionQ?nbsp;
在前面的例子里,我们通过“依赖倒置”原则Q最大限度地减弱了应用程序Copycdcd提供的服务ReadQWrite之间的依赖关pR但是,如果需要把Copy()函数也实现在cd中,又会发生什么情况呢Q假讑֜cd中实C?#8220;服务c?#8221;Q?#8220;服务c?#8221;提供Copy()Ҏ供应用程序用。应用程序用时Q首先创?#8220;服务c?#8221;的实例,调用其中的Copy()函数?#8220;服务c?#8221;的实例初始化时会创徏KeyboardReader 和PrinterWritercȝ实例对象。如?8所C?
l论
分离接口和实现是Z有效地控制依赖关pȝ最初尝试,而纯_的抽象接口更好地隔M怺依赖的两个模块,“依赖倒置”?“控制反{”原则从不同的角度描述了利用抽象接口消解耦合的动机,GoF的设计模式正是这一动机的完体现。具体类的创E是另一U常见的依赖关系Q?#8220;依赖注入”模式可以把具体类的创E集中到合适的位置Q这一动机和GoF的创建型模式有相g处?br> q些原则Ҏ们的实践有很好的指导作用Q但它们不是圣经Q在不同的场合可能会有不同的变化Q我们应该在开发过E中Ҏ需求变化的可能性灵z运用?
参考文?nbsp;
[PicoContainer 2004] http://www.picocontainer.org/Inversion+of+Control
[Martin 1996] The Dependency Inversion Principle, Robert C. Martin, C++ Report, May, 1996, http://www.objectmentor.com/resources/articles/dip.pdf
[Fowler 2001] Martin Fowler: Reducing Coupling. IEEE Software 18(4): 102-104 (2001) http://www.martinfowler.com/ieeeSoftware/coupling.pdf
[Folwer 2004] Inversion of Control Containers and the Dependency Injection pattern http://martinfowler.com/articles/injection.html
[透明2004] 透明QInversion of Control Containers and the Dependency Injection pattern 的译?a >http://gigix.blogdriver.com/gigix/inc/DependencyInjection.pdf
[王咏? 王咏?2004] 王咏? 王咏? 道法自然—面向对象实跉|? 电子工业出版C? 2004
Singleton模式׃ؓ我们提供了这样实现的可能。用Singleton的好处还在于可以节省内存Q因为它限制?br>实例的个敎ͼ有利于Java垃圾回收Qgarbage collectionQ?/p>
使用Singleton注意事项Q?br>有时在某些情况下Q用Singletonq不能达到Singleton的目的,如有多个Singleton对象同时被不同的c?br>装入器装载;在EJBq样的分布式pȝ中用也要注意这U情况,因ؓEJB是跨服务器,跨JVM?/p>
单态模式的演化Q?br>单态模式是个简单的模式Q但是这个简单的模式也有很多复杂的东ѝ?br>
Q注意:在这里补充一下,现在单态模式其实有一个写法是不错的见q里Q?a href="http://www.aygfsteel.com/dreamstone/archive/2007/02/27/101000.html">http://www.aygfsteel.com/dreamstone/archive/2007/02/27/101000.htmlQ但q是看完q篇文章Q因释的事情是不一LQ这里说的是Z么double-checked不能使用.Q?br>一Q首先最单的单态模式,单态模?
import java.util.*;
class Singleton
{
private static Singleton instance;
private Vector v;
private boolean inUse;
private Singleton()
{
v = new Vector();
v.addElement(new Object());
inUse = true;
}
public static Singleton getInstance()
{
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
}
q个单态模式是不安全的Qؓ什么说?Q因为没考虑多线E,如下情况
Thread 1 调用getInstance() ҎQƈ且判断instance是nullQ然後进入if模块Q?br>在实例化instance之前Q?br>Thread 2抢占了Thread 1的cpu
Thread 2 调用getInstance() ҎQƈ且判断instance是nullQ然後进入if模块Q?br>Thread 2 实例化instance 完成Q返?br>Thread 1 再次实例化instance
q个单态已l不在是单?/p>
二,Z解决刚才的问题:单态模?
public static synchronized Singleton getInstance()
{
if (instance == null) //1
instance = new Singleton(); //2
return instance; //3
}
采用同步来解冻Iq种方式解决了问题,但是仔细分析
正常的情况下只有W一ơ时候,q入对象的实例化Q须要同步,
其它时候都是直接返回已l实例化好的instance不须要同步,
大家都知到在一个多U程的程序中Q如果同步的消耗是很大的,很容易造成瓉
三,Z解决上边的问题:单态模?Q加入同?br>public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) {
instance = new Singleton();
}
}
return instance;
}
同步Ҏ块同步,而不使用函数同步Q但是仔l分析,
又回C模式一的状态,再多U程的时候根本没有解决问?/p>
四,Z对应上边的问题:单态模?Q也是很多人采用的Double-checked locking
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
if (instance == null) //2
instance = new Singleton(); //3
}
}
return instance;
}
q样Q模式一中提到的问题解决了。不会出现多ơ实例化的现?br>当第一ơ进入的时候,保正实例化时候的单?在实例化后,多线E访问的时候直接返回,不须要进入同步模块,
既实C单态,又没有损失性能。表面上看我们的问题解决了,但是再仔l分析:
我们来假象这中情况:
Thread 1 :q入?/3位置Q执行new Singleton()Q但是在构造函数刚刚开始的时候被Thread2抢占cpu
Thread 2 :q入getInstance()Q判断instance不等于null,q回instanceQ?br>Qinstance已经被newQ已l分配了内存I间,但是没有初始化数?
Thread 2 :利用q回的instance做某些操做,p|或者异?br>Thread 1 :取得cpu初始化完?br>q程中可能有多个U程取到了没有完成的实例Qƈ用这个实例作出某些操做?br>Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q?br>出现以上的问题是因ؓ
mem = allocate(); //分配内存
instance = mem; //标记instance非空
//未执行构造函?thread 2从这里进?br>ctorSingleton(instance); //执行构造函?br> //q回instance
Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-
五,证明上边的假x可能发生的,字节码是用来分析问题的最好的工具Q可以利用它来分?br>下边一D늨序:Qؓ了分析方便,所以渐了内容Q?br>字节码的使用Ҏ见这里,利用字节码分析问?/a>
class Singleton
{
private static Singleton instance;
private boolean inUse;
private int val;
private Singleton()
{
inUse = true;
val = 5;
}
public static Singleton getInstance()
{
if (instance == null)
instance = new Singleton();
return instance;
}
}
得到的字节码
;asm code generated for getInstance
054D20B0 mov eax,[049388C8] ;load instance ref
054D20B5 test eax,eax ;test for null
054D20B7 jne 054D20D7
054D20B9 mov eax,14C0988h
054D20BE call 503EF8F0 ;allocate memory
054D20C3 mov [049388C8],eax ;store pointer in
;instance ref. instance
;non-null and ctor
;has not run
054D20C8 mov ecx,dword ptr [eax]
054D20CA mov dword ptr [ecx],1 ;inline ctor - inUse=true;
054D20D0 mov dword ptr [ecx+4],5 ;inline ctor - val=5;
054D20D7 mov ebx,dword ptr ds:[49388C8h]
054D20DD jmp 054D20B0
上边的字节码证明Q猜x有可能实现的
六:好了Q上边证明Double-checked locking可能出现取出错误数据的情况,那么我们q是可以解决?br>public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
Singleton inst = instance; //2
if (inst == null)
{
synchronized(Singleton.class) { //3
inst = new Singleton(); //4
}
instance = inst; //5
}
}
}
return instance;
}
利用Double-checked locking 两次同步Q中间变?解决上边的问题?br>Q下边这D话我只能简单的理解Q翻译过来不好,所以保留原文,list 7是上边的代码Qlist 8是下边的
The code in Listing 7 doesn't work because of the current definition of the memory model.
The Java Language Specification (JLS) demands that code within a synchronized block
not be moved out of a synchronized block. However, it does not say that
code not in a synchronized block cannot be moved into a synchronized block.
A JIT compiler would see an optimization opportunity here.
This optimization would remove the code at
//4 and the code at //5, combine it and generate the code shown in Listing 8:Q?br>-------------------------------------------------
list 8
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
Singleton inst = instance; //2
if (inst == null)
{
synchronized(Singleton.class) { //3
//inst = new Singleton(); //4
instance = new Singleton();
}
//instance = inst; //5
}
}
}
return instance;
}
If this optimization takes place, you have the same out-of-order write problem we discussed earlier.
如果q个优化发生Q将再次发生上边提到的问题,取得没有实例化完成的数据?br>-------------------------------------------------
以下部分Z避免我翻译错误误导打Ӟ保留原文
Another idea is to use the keyword volatile for the variables inst and instance.
According to the JLS (see Resources), variables declared volatile are supposed to
be sequentially consistent, and therefore, not reordered.
But two problems occur with trying to use volatile to fix the problem with
double-checked locking:
The problem here is not with sequential consistency.
Code is being moved, not reordered.
Many JVMs do not implement volatile correctly regarding sequential consistency anyway.
The second point is worth expanding upon. Consider the code in Listing 9:
Listing 9. Sequential consistency with volatile
class test
{
private volatile boolean stop = false;
private volatile int num = 0;
public void foo()
{
num = 100; //This can happen second
stop = true; //This can happen first
//...
}
public void bar()
{
if (stop)
num += num; //num can == 0!
}
//...
}
According to the JLS, because stop and num are declared volatile,
they should be sequentially consistent. This means that if stop is ever true,
num must have been set to 100.
However, because many JVMs do not implement the sequential consistency feature of volatile,
you cannot count on this behavior.
Therefore, if thread 1 called foo and thread 2 called bar concurrently,
thread 1 might set stop to true before num is set to 100.
This could lead thread 2 to see stop as true, but num still set to 0.
There are additional problems with volatile and the atomicity of 64-bit variables,
but this is beyond the scope of this article.
See Resources for more information on this topic.
单的理解上边q段话,使用volatile有可能能解决问题Qvolatile被定义用来保正一致性,但是很多虚拟?br>q没有很好的实现volatileQ所以用它也会存在问题?/p>
最l的解决ҎQ?br> Q?Q,单态模?Q用同步方?br> Q?Q,攑ּ同步Q用一个静态变量,如下
class Singleton
{
private Vector v;
private boolean inUse;
private static Singleton instance = new Singleton();
private Singleton()
{
v = new Vector();
inUse = true;
//...
}
public static Singleton getInstance()
{
return instance;
}
}
但用静态变量也会存在问题,问题?q篇文章
而且如在文章开头提到的Q用EJB跨服务器Q跨JVM的情况下Q单态更是问?/p>
好了是不是感觉单态模式根本没法用了Q其实上辚w是特D情况,q中Ҏ情况的出现是有条件的Q只?br>Ҏ你的具体应用Q回避一些,p解决问题Q所以单态还是可以用的。但是在使用前慎重,自己考虑好自?br>的情况适合哪种情况?/p>
参?br>http://www.jdon.com/designpatterns/singleton.htm
Double-checked locking and the Singleton pattern
When is a singleton not a singleton?