Information Technology Reference
In-Depth Information
The “optimization” is to avoid acquiring the lock if the object has not already been
allocated, but to avoid allocating the object multiple times by acquiring the lock and
rechecking the status before allocating the object:
Singleton*Singleton::pInstance=NULL;
//BUG! Don'tdothis!
Singleton*Singleton::instance(){
if(pInstance==NULL){
lock->Acquire();
if(pInstance==NULL){
pInstance=newInstance();
classSingleton{
public:
staticSingleton*instance();
Locklock;
...
private:
staticSingleton*pInstance;
}
lock->Release();
};
}
returnpInstance;
}
Singleton.h header file Singleton.cc implementation file
Although the intuition is appealing, this code does not work. The problem is that
the statement
pInstance=newInstance()
is not an an atomic operation; in fact, it
comprises at least three steps:
1. Allocate memory for a
Singleton
object
2. Initialize the
Singleton
object's memory by running the constructor
3. Make
pInstance
point to this newly constructed object
The problem is that, modern compilers and hardware architectures can reorder
these events. Thus, it is possible for thread 1 to execute the first step and then the
third step; then thread 2 can call
instance()
, see that
pInstance
is non-null, return it,
and begin using this object before thread 1 initializes it.
Discussion.
This is just an example of dangers that lurk when you try to elide locks;
the lesson applies more broadly. This example is extremely simple—fewer than 10 lines
of code with very simple logic—yet a number of published solutions have ben wrong. For
notes, some tempting solutions using temporary variables and the
volatile
keyword
cusses a wide range of non-working solutions in Java.
This type of optimization is risky and often does not end up providing significant
performance gains in program. Most programmers should not consider them. Even
expert programmers should habitually stick to simpler programming patterns like the
ones we discuss in the body of the text and should consider optimizations like double-
checked locking only rarely and only when performance measurements and profiling
indicate that the optimizations will matter for overall performance.
7
There are (non-portable) solutions in C/C++, but we won't give them here. If you
want to try these advanced techniques, then you should read the literature for more in-depth
discussions so that you deeply understand the issues and why various appealing \solutions"
fail.