??xml version="1.0" encoding="utf-8" standalone="yes"?> Signed by :
David Bacon (IBM Research)
Joshua Bloch (Javasoft),
Jeff Bogda,
Cliff Click (Hotspot JVM project),
Paul Haahr,
Doug Lea,
Tom May,
Jan-Willem Maessen,
Jeremy Manson,
John D. Mitchell (jGuru)
Kelvin Nilsen,
Bill Pugh,
Emin Gun Sirer
Double-Checked Locking is widely cited and used
as an efficient method for implementing
lazy initialization
in a multithreaded environment.
Unfortunately, it
will not work reliably in a platform independent way
when implemented in Java, without additional synchronization.
When implemented in other languages, such as C++, it depends on
the memory model of the processor, the reorderings performed by
the compiler and the interaction between the compiler and the synchronization
library. Since none of these are specified in a language such as C++,
little can be said about the situations in which it will work. Explicit
memory barriers can be used to make it work in C++, but these barriers are
not available in Java.
To first explain the desired behavior, consider the following code:
大家都知道,在用C++来实现Singleton模式的时候通常把构造函数声明ؓU有的或者保护的。同时声明一个公有的静态的伪构造函敎ͼ通过它来调用真正的构造函数。在实现q个伪构造函数的时候通常有两U方式:
class Singleton;
static Singleton& Singleton:;fakeSingleton()
{
static Singleton s;
return s;
}
W二U方?
class Singleton{
public:
static Singleton* fakeSingleton();
...
private:
Singleton();
static Singleton * _instance;
}
Singleton* Singleton::fakesinketon()
{
if( _instance==NULL)
_instance=new Singleton();
return _instance;
}
对于q两U方式我觉得W一U更好一些,理由是,如果有两个以上的U程同时讉K伪构造函数的时候有可能同时q入if 控制块,q样有可能产生两个实例Q!因此必须采用Ҏ的保护机制来控制同步。而第一U方式不存在q样的问题?
请高手指点!我不明白的是Qؓ什么书上的例子q较多的采用W二U方法?莫非它有自己的优势?Q?
// Single threaded version
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
helper = new Helper();
return helper;
}
// other functions and members...
}
If this code was used in a multithreaded context, many things could go wrong. Most obviously, two or more Helper objects could be allocated. (We'll bring up other problems later). The fix to this is simply to synchronize the getHelper() method:
// Correct multithreaded version |
The code above performs synchronization every time getHelper() is called. The double-checked locking idiom tries to avoid synchronization after the helper is allocated:
// Broken multithreaded version |
Unfortunately, that code just does not work in the presence of either optimizing compilers or shared memory multiprocessors.
There are lots of reasons it doesn't work. The first couple of reasons we'll describe are more obvious. After understanding those, you may be tempted to try to devise a way to "fix" the double-checked locking idiom. Your fixes will not work: there are more subtle reasons why your fix won't work. Understand those reasons, come up with a better fix, and it still won't work, because there are even more subtle reasons.
Lots of very smart people have spent lots of time looking at this. There is no way to make it work without requiring each thread that accesses the helper object to perform synchronization.
The most obvious reason it doesn't work it that the writes that initialize the Helper object and the write to the helper field can be done or perceived out of order. Thus, a thread which invokes getHelper() could see a non-null reference to a helper object, but see the default values for fields of the helper object, rather than the values set in the constructor.
If the compiler inlines the call to the constructor, then the writes that initialize the object and the write to the helper field can be freely reordered if the compiler can prove that the constructor cannot throw an exception or perform synchronization.
Even if the compiler does not reorder those writes, on a multiprocessor the processor or the memory system may reorder those writes, as perceived by a thread running on another processor.
Doug Lea has written a more detailed description of compiler-based reorderings.
Paul Jakubik found an example of a use of double-checked locking that did not work correctly. A slightly cleaned up version of that code is available here.
When run on a system using the Symantec JIT, it doesn't work. In particular, the Symantec JIT compiles
singletons[i].reference = new Singleton();
to the following (note that the Symantec JIT using a handle-based object allocation system).
0206106A mov eax,0F97E78h
0206106F call 01F6B210 ; allocate space for
; Singleton, return result in eax
02061074 mov dword ptr [ebp],eax ; EBP is &singletons[i].reference
; store the unconstructed object here.
02061077 mov ecx,dword ptr [eax] ; dereference the handle to
; get the raw pointer
02061079 mov dword ptr [ecx],100h ; Next 4 lines are
0206107F mov dword ptr [ecx+4],200h ; Singleton's inlined constructor
02061086 mov dword ptr [ecx+8],400h
0206108D mov dword ptr [ecx+0Ch],0F84030h
As you can see, the assignment to singletons[i].reference is performed before the constructor for Singleton is called. This is completely legal under the existing Java memory model, and also legal in C and C++ (since neither of them have a memory model).
Given the explanation above, a number of people have suggested the following code:
// (Still) Broken multithreaded version |
This code puts construction of the Helper object inside an inner synchronized block. The intuitive idea here is that there should be a memory barrier at the point where synchronization is released, and that should prevent the reordering of the initialization of the Helper object and the assignment to the field helper.
Unfortunately, that intuition is absolutely wrong. The rules for synchronization don't work that way. The rule for a monitorexit (i.e., releasing synchronization) is that actions before the monitorexit must be performed before the monitor is released. However, there is no rule which says that actions after the monitorexit may not be done before the monitor is released. It is perfectly reasonable and legal for the compiler to move the assignment helper = h; inside the synchronized block, in which case we are back where we were previously. Many processors offer instructions that perform this kind of one-way memory barrier. Changing the semantics to require releasing a lock to be a full memory barrier would have performance penalties.
There is something you can do to force the writer to perform a full bidirectional memory barrier. This is gross, inefficient, and is almost guaranteed not to work once the Java Memory Model is revised. Do not use this. In the interests of science, Do not use it.
However , even with a full memory barrier being performed by the thread that initializes the helper object, it still doesn't work.
The problem is that on some systems, the thread which sees a non-null value for the helper field also needs to perform memory barriers.
Why? Because processors have their own locally cached copies of memory. On some processors, unless the processor performs a cache coherence instruction (e.g., a memory barrier), reads can be performed out of stale locally cached copies, even if other processors used memory barriers to force their writes into global memory.
I've created a separate web page with a discussion of how this can actually happen on an Alpha processor.
For most applications, the cost of simply making the getHelper() method synchronized is not high. You should only consider this kind of detailed optimizations if you know that it is causing a substantial overhead for an application.
Very often, more high level cleverness, such as using the builtin mergesort rather than handling exchange sort (see the SPECJVM DB benchmark) will have much more impact.
If the singleton you are creating is static (i.e., there will only be one Helper created), as opposed to a property of another object (e.g., there will be one Helper for each Foo object, there is a simple and elegant solution.
Just define the singleton as a static field in a separate class. The semantics of Java guarantee that the field will not be initialized until the field is referenced, and that any thread which accesses the field will see all of the writes resulting from initializing that field.
class HelperSingleton { |
Although the double-checked locking idiom cannot be used for references to objects, it can work for 32-bit primitive values (e.g., int's or float's). Note that it does not work for long's or double's, since unsynchronized reads/writes of 64-bit primitives are not guaranteed to be atomic.
// Correct Double-Checked Locking for 32-bit primitives |
In fact, assuming that the computeHashCode function always returned the same result and had no side effects (i.e., idempotent), you could even get rid of all of the synchronization.
// Lazy initialization 32-bit primitives |
It is possible to make the double checked locking pattern work if you have explicit memory barrier instructions. For example, if you are programming in C++, you can use the code from Doug Schmidt et al.'s book:
// C++ implementation with explicit memory barriers |
Alexander Terekhov (TEREKHOV@de.ibm.com) came up clever suggestion for implementing double checked locking using thread local storage. Each thread keeps a thread local flag to determine whether that thread has done the required synchronization.
class Foo { |
The performance of this technique depends quite a bit on which JDK implementation you have. In Sun's 1.2 implementation, ThreadLocal's were very slow. They are significantly faster in 1.3, and are expected to be faster still in 1.4. Doug Lea analyzed the performance of some techniques for implementing lazy initialization.
As of JDK5, there is a new Java Memory Model and Thread specification.
JDK5 and later extends the semantics for volatile so that the system will not allow a write of a volatile to be reordered with respect to any previous read or write, and a read of a volatile cannot be reordered with respect to any following read or write. See this entry in Jeremy Manson's blog for more details.
With this change, the Double-Checked Locking idiom can be made to work by declaring the helper field to be volatile. This does not work under JDK4 and earlier.
// Works with acquire/release semantics for volatile |
If Helper is an immutable object, such that all of the fields of Helper are final, then double-checked locking will work without having to use volatile fields. The idea is that a reference to an immutable object (such as a String or an Integer) should behave in much the same way as an int or float; reading and writing references to immutable objects are atomic.
本文档给计模式之——AbstractFactory模式的简化诠释,q给出其C++实现?/p>
Project |
Design Pattern ExplanationQBy K_EckelQ?/p> |
Authorization |
Free Distributed but Ownership Reserved |
Date |
|
Test Bed |
MS Visual C++ 6.0 |
在本文档的写作中Q参考了以下的资源,在此列出表示感谢Q?/p>
u 书籍
[GoF 2000]QGoF,Design Patterns-Elements of Reusable Object-Oriented Software
Addison-Wesley 2000/9.
[Martine 2003]QRobert C.Martine, Agile Software Development Principles, Patterns, and Practices, Pearson Education, 2003.
Author |
K_Eckel |
State |
Candidate for Master’s Degree School of |
E_mail |
假设我们要开发一ƾ游戏,当然Z吸引更多的h玩,游戏隑ֺ不能太大Q让大家都没有信心了Q估计游戏也没有前途了Q,但是也不能太单(没有挑战性也不符合玩家的心理Q。于是我们就可以采用q样一U处理策略:为游戏设立等U,初、中U、高U甚xBTU。假设也是过关的游戏Q每个关卡都有一些怪物QmonsterQ守着Q玩家要把这些? 物干掉才可以q关。作为开发者,我们׃得不创徏怪物的类Q然后初U怪物、中U怪物{都l承自怪物c(当然不同U类的则需要另创徏c,但是模式相同Q。在 每个兛_Q我们都要创建怪物的实例,例如初创建初U怪物Q有很多U类Q、中U创ZU怪物{。可以想象在q个pȝ中,会有成千上万的怪物实例要创 建,问题是还要保证创建的时候不会出错:初不能创徏BTU的怪物Q玩家就郁闷了,玩家一郁闷Q游戏也挂挂了Q,反之也不可以?/p>
AbstractFactory模式是用来解决q类问题的:要创Zl相x者相互依赖的对象?/p>
AbstractFactory模式典型的结构图为:
?font face="Times New Roman">2-1Q?font face="Times New Roman">AbstractFactoryPatternl构?/p>
AbstractFactory模式关键是这一l对象的创徏装C个用于创建对象的c(ConcreteFactoryQ中Q维护这样一个创建类Ll护n多相兛_象的创徏q程要简单的多?/p>
AbstractFactory模式的实现比较简单,q里Z方便初学者的学习和参考,给出完整的实现代码Q所有代码采?font face="Times New Roman">C++实现Qƈ?font face="Times New Roman">VC 6.0下测试运行)?br>
代码片断1Q?/font>Product.h #ifndef _PRODUCT_H_ class AbstractProductA protected: private: }; class AbstractProductB protected: private: }; class ProductA1:public AbstractProductA ~ProductA1(); protected: private: }; class ProductA2:public AbstractProductA ~ProductA2(); protected: private: }; class ProductB1:public AbstractProductB ~ProductB1(); protected: private: }; class ProductB2:public AbstractProductB ~ProductB2(); protected: private: }; #endif //~_PRODUCT_H_ |
代码片断2Q?/font>Product.cpp #include "Product.h" #include <iostream> AbstractProductA::AbstractProductA() } AbstractProductA::~AbstractProductA() } AbstractProductB::AbstractProductB() } AbstractProductB::~AbstractProductB() } ProductA1::ProductA1() ProductA1::~ProductA1() } ProductA2::ProductA2() ProductA2::~ProductA2() } ProductB1::ProductB1() ProductB1::~ProductB1() } ProductB2::ProductB2() ProductB2::~ProductB2() } |
代码片断3Q?/font>AbstractFactory.h #ifndef _ABSTRACTFACTORY_H_ class AbstractProductA; class AbstractFactory virtual AbstractProductA* CreateProductA() = 0; virtual AbstractProductB* CreateProductB() = 0; protected: private: }; class ConcreteFactory1:public AbstractFactory ~ConcreteFactory1(); AbstractProductA* CreateProductA(); AbstractProductB* CreateProductB(); protected: private: }; class ConcreteFactory2:public AbstractFactory ~ConcreteFactory2(); AbstractProductA* CreateProductA(); AbstractProductB* CreateProductB(); protected: private: }; |
代码片断4Q?/font>AbstractFactory.cpp #include "AbstractFactory.h" #include <iostream> AbstractFactory::AbstractFactory() } AbstractFactory::~AbstractFactory() } ConcreteFactory1::ConcreteFactory1() } ConcreteFactory1::~ConcreteFactory1() } AbstractProductA* ConcreteFactory1::CreateProductA() AbstractProductB* ConcreteFactory1::CreateProductB() ConcreteFactory2::ConcreteFactory2() } ConcreteFactory2::~ConcreteFactory2() } AbstractProductA* ConcreteFactory2::CreateProductA() AbstractProductB* ConcreteFactory2::CreateProductB() |
代码片断5Q?/font>main.cpp #include "AbstractFactory.h" #include <iostream> int main(int argc,char* argv[]) cf1->CreateProductA(); AbstractFactory* cf2 = new ConcreteFactory2(); return 0; |
AbstractFactory模式的实C码很单,在测试程序中可以看到Q当我们要创Zl对象(ProductA1QProductA2Q的时候我们只用维护一个创建对象(ConcreteFactory1Q,大大化了l护的成本和工作?/font>
AbstractFactory模式和Factory模式的区别是初学Q用)设计模式时候的一个容易引起困惑的地方。实际上QAbstractFactory模式是ؓ创徏一l(有多c)相关或依赖的对象提供创徏接口Q而Factory模式正如我在相应的文档中分析的是?strong>一c?/strong>对象提供创徏接口或gq对象的创徏到子cM实现。ƈ且可以看刎ͼAbstractFactory模式通常都是使用Factory模式实现QConcreteFactory1Q?/font>
其中Q聚合关p(AggregationQ,合成关系QCompositionQ属于关联关p(AssociationQ?/font>
一般关p表Cؓl承或实现关p?is a)Q关联关p表Cؓ变量(has a )Q依赖关p表Cؓ函数中的参数(use a)?/font>
一般化关系Q表CZؓcMcM间的l承关系Q接口与接口之间的承,cd接口的实现关pR?br> 表示ҎQ?用一个空心箭_实线Q箭头指向父cR或I心头Q虚U,如果父类是接口?/font>
兌关系Q类与类之间的联接,它一个类知道另一个类的属性和Ҏ?br> 表示ҎQ用 实线Q箭_ 头指向被用的cR?/font>
聚合关系Q是兌关系的一U,是强的关联关pR聚合关pL整体和个体的关系。关联关pȝ两个cd于同一层次上,啊聚合关pM个类处于不同的层ơ,一个是整体Q一个是部分?br> 表示ҎQ空心菱形+实线Q箭_头指向部分?/font>
合成关系Q是兌关系的一U,是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期Q合成关pM能共享?br> 表示ҎQ实心菱形+实线Q箭_
依赖关系Q是cMcM间的q接Q表CZ个类依赖于另一个类的定义。例如如果A依赖于BQ则B体现为局部变量,Ҏ的参数、或静态方法的调用?br> 表示ҎQ虚U+头
本文档给计模式之——Observer模式的简化诠释,q给出其C++实现?/p>
Project |
Design Pattern ExplanationQBy K_EckelQ?/p> |
Authorization |
Free Distributed but Ownership Reserved |
Date |
|
Test Bed |
MS Visual C++ 6.0 |
在本文档的写作中Q参考了以下的资源,在此列出表示感谢Q?/p>
u 书籍
[GoF 2000]QGoF,Design Patterns-Elements of Reusable Object-Oriented Software
Addison-Wesley 2000/9.
[Martine 2003]QRobert C.Martine, Agile Software Development Principles, Patterns, and Practices, Pearson Education, 2003.
Author |
K_Eckel |
State |
Candidate for Master’s Degree School of |
E_mail |
Observer模式应该可以说是应用最多、媄响最q的模式之一Q因为Observer的一个实例Model/View/ControlQMVCQ结构在pȝ开发架构设计中有着很重要的C和意义,MVC实现了业务逻辑和表C层的解耦?strong>个h也认?/strong>Observer模式是Y件开发过E中必须要掌握和使用的模式之一。在MFC中,Doc/ViewQ文档视囄构)提供了实现MVC的框架结构(有一个从设计模式QObserver模式Q的角度分析分析Doc/View的文章正在进一步的撰写当中Q遗憄是时_Q)。在Java阵容中,Struts则提供和MFC中Doc/Viewl构cM的实现MVC的框架。另外Java语言本n提供了Observer模式的实现接口,q将在讨Zl出?/p>
当然QMVC只是Observer模式的一个实例。Observer模式要解决的问题为:建立一个一QSubjectQ对多(ObserverQ? 的依赖关p,q且做到?#8220;一”变化的时候,依赖q个“一”的多也能够同步改变。最常见的一个例子就是:对同一l数据进行统计分析时候,我们希望能够提供? UŞ式的表示Q例如以表格q行l计昄、柱状图l计昄、百分比l计昄{)。这些表C都依赖于同一l数据,我们当然需要当数据改变的时候,所有的l计? 昄都能够同时改变。Observer模式是解决了这一个问题?/p>
Observer模式典型的结构图为:
?font face="Times New Roman">2-1Q?font face="Times New Roman">Observer Patternl构?/p>
q里的目?font face="Times New Roman">Subject提供依赖于它的观察?font face="Times New Roman">Observer的注册(AttachQ和注销Q?font face="Times New Roman">DetachQ操作,q且提供了得依赖于它的所有观察者同步的操作Q?font face="Times New Roman">NotifyQ。观察?font face="Times New Roman">Observer则提供一?font face="Times New Roman">Update操作Q注意这里的Observer?font face="Times New Roman">Update操作q不?font face="Times New Roman">Observer改变?font face="Times New Roman">Subject目标状态的时候就对自p行更斎ͼq个更新操作要gq到Subject对象发出Notify通知所?font face="Times New Roman">Observerq行修改Q调?font face="Times New Roman">UpdateQ?/p>
Observer模式的实现有些特点,q里Z方便初学者的学习和参考,给出完整的实现代码Q所有代码采?font face="Times New Roman">C++实现Qƈ?font face="Times New Roman">VC 6.0下测试运行)?br>
代码片断1Q?/font>Subject.h #ifndef _SUBJECT_H_ #include <list> typedef string State; class Observer; class Subject virtual void Attach(Observer* obv); virtual void Detach(Observer* obv); virtual void Notify(); virtual void SetState(const State& st) = 0; virtual State GetState() = 0; protected: private: }; class ConcreteSubject:public Subject ~ConcreteSubject(); State GetState(); void SetState(const State& st); protected: private: }; #endif //~_SUBJECT_H_ |
代码片断2Q?/font>Subject.cpp #include "Subject.h" #include <iostream> typedef string state; Subject::Subject() } Subject::~Subject() void Subject::Attach(Observer* obv) void Subject::Detach(Observer* obv) void Subject::Notify() it = _obvs->begin(); for (;it != _obvs->end();it++) (*it)->Update(this); ConcreteSubject::ConcreteSubject() ConcreteSubject::~ConcreteSubject()
void ConcreteSubject::SetState(const State& st) |
代码片断3Q?/font>Observer.h #ifndef _OBSERVER_H_ #include "Subject.h" #include <string> typedef string State; class Observer virtual void Update(Subject* sub) = 0; virtual void PrintInfo() = 0; protected: State _st; private: }; class ConcreteObserverA:public Observer virtual ~ConcreteObserverA(); //传入Subject作ؓ参数Q这样可以让一?/font>View属于多个?/font>Subject?/font> void PrintInfo(); protected: private: }; class ConcreteObserverB:public Observer virtual ~ConcreteObserverB(); //传入Subject作ؓ参数Q这样可以让一?/font>View属于多个?/font>Subject?/font> void PrintInfo(); protected: private: }; #endif //~_OBSERVER_H_ |
代码片断4Q?/font>Observer.cpp #include "Observer.h" #include <iostream> Observer::Observer() } Observer::~Observer() }
_sub->Attach(this); ConcreteObserverA::~ConcreteObserverA() if (_sub != 0) Subject* ConcreteObserverA::GetSubject() void ConcreteObserverA::PrintInfo() void ConcreteObserverA::Update(Subject* sub) PrintInfo(); ConcreteObserverB::ConcreteObserverB(Subject* sub) _sub->Attach(this); ConcreteObserverB::~ConcreteObserverB() if (_sub != 0) Subject* ConcreteObserverB::GetSubject() void ConcreteObserverB::PrintInfo() void ConcreteObserverB::Update(Subject* sub) PrintInfo(); |
代码片断5Q?/font>main.cpp #include "Subject.h" #include <iostream> int main(int argc,char* argv[]) Observer* o1 = new ConcreteObserverA(sub); Observer* o2 = new ConcreteObserverB(sub); sub->SetState("old"); sub->Notify(); sub->SetState("new"); //也可以由Observer调用 sub->Notify(); return 0; |
在Observer模式的实CQSubjectl护一个list作ؓ存储其所有观察者的容器。每当调用Notify操作遍历list中的Observer对象Qƈq播通知改变状态(调用Observer的Update操作Q。目标的状态state可以由Subject自己改变Q示例)Q也可以由Observer的某个操作引起state的改变(可调用Subject的SetState操作Q。Notify操作可以由Subject目标dq播Q示例)Q也可以由Observer观察者来调用Q因为Observerl护一个指向Subject的指针)?/font>
q行CZE序Q可以看到当Subject处于状?#8220;old”时候,依赖于它的两个观察者都昄“old”Q当目标状态改变ؓ“new”的时候,依赖于它的两个观察者也都改变ؓ“new”?/font>
Observer是媄响极为深q的模式之一Q也是在大型pȝ开发过E中要用到的模式之一。除了MFC、Struts提供了MVC的实现框Ӟ在Java语言中还提供了专门的接口实现Observer模式Q通过专门的类Observable及Observer接口来实现MVC~程模式Q其UML囑֏以表CZؓQ?/font>
Java中实现MVC的UML图?/font>
q里的Observer是观察者,Observable则充当目标Subject的角艌Ӏ?/font>
Observer模式也称为发布-订阅Qpublish-subscribeQ,目标是通知的发布者,观察者则是通知的订阅者(接受通知Q?/font>
Definition:
A class is a pure interface if it meets the following requirements:
= 0
") methods
and static methods (but see below for destructor).
Interface
suffix.
An interface class can never be directly instantiated because of the pure virtual method(s) it declares. To make sure all implementations of the interface can be destroyed correctly, they must also declare a virtual destructor (in an exception to the first rule, this should not be pure). See Stroustrup, The C++ Programming Language, 3rd edition, section 12.4 for details.
Pros:
Tagging a class with the Interface
suffix lets
others know that they must not add implemented methods or non
static data members. This is particularly important in the case of
multiple inheritance.
Additionally, the interface concept is already well-understood by
Java programmers.
Cons:
The Interface
suffix lengthens the class name, which
can make it harder to read and understand. Also, the interface
property may be considered an implementation detail that shouldn't
be exposed to clients.
Decision:
A class may end with Interface
only if it meets the
above requirements. We do not require the converse, however:
classes that meet the above requirements are not required to end
with Interface
.