Hungry Chinese singleton mode: the instance object has not been obtained yet, the instance object has already been generated
class Singleton { public: static Singleton* getInstance() { return &instance; } private: static Singleton instance; Singleton() { } Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; //Static member variables initialized outside the class Singleton Singleton::instance; int main() { Singleton *p1 = Singleton::getInstance(); Singleton *p2 = Singleton::getInstance(); Singleton *p3 = Singleton::getInstance(); //If there is no privatized copy constructor, a copy will occur here //Singleton p4 = *p1; cout << "p1: " << p1 << " p2: " << p2 << " p3: " << p3 /*<< " p4: " << &p4*/ << endl; return 0; }
The Hungry-style singleton mode must be thread-safe, because the static member variables are initialized after the program starts, and there will be no thread safety issues. But there are some disadvantages. If the singleton is never used in the program, and a lot of things are done in the constructor, such as opening files, etc., it will undoubtedly affect the efficiency.
Lazy singleton mode: the only instance object is not generated until the first time it is acquired
class Singleton { public: static Singleton* getInstance() { if (instance == nullptr) { instance = new Singleton(); } return instance; } private: static Singleton *instance; Singleton() { } Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; Singleton* Singleton::instance = nullptr; int main() { Singleton *p1 = Singleton::getInstance(); Singleton *p2 = Singleton::getInstance(); Singleton *p3 = Singleton::getInstance(); cout << "p1: " << p1 << " p2: " << p2 << " p3: " << p3 << endl; return 0; }
The lazy style solves the problem of reinitialization when it is used, but it is not thread-safe.
In a multi-threaded environment, problems may arise when constructing objects:
static Singleton* getInstance() { if (instance == nullptr) { /* Open up memory construct object Assign value to instance */ instance = new Singleton(); } return instance; }
For example, when constructing an object, it will go through three steps. The first thread comes in and finds that the instance is empty, and starts to open up memory, but has not yet assigned a value to the instance. At this time, the instance is still empty. At this time, the second thread can also enter the construction, and the function cannot re-entry. At the same time, in order to speed up the operation of modern computers, the operations of constructing objects and assigning values ββββto instances may be completely reversed. At this time, if the second thread finds that instance is not empty and returns directly, an error will also occur, because the assignment is completed at this time. Not constructed yet.
One solution is to add a lock:
mutex mtx; class Singleton { public: static Singleton* getInstance() { lock_guard<std::mutex> guard(mtx); if (instance == nullptr) { /* Open up memory construct object Assign value to instance */ instance = new Singleton(); } return instance; } ... }
But this situation is not friendly to single-threaded, every time you have to lock, the granularity of the lock is too large. Now put the lock inside the if
mutex mtx; class Singleton { public: static Singleton* getInstance() { if (instance == nullptr) { lock_guard<std::mutex> guard(mtx); /* Open up memory construct object Assign value to instance */ instance = new Singleton(); } return instance; } ... }
At this time, there is also a problem. The first thread enters if, locks, and new objects. At this time, the second thread judges that the instance is empty and enters the waiting lock. After the first thread is new, the second thread gets the lock. The new object will also be used.
So at this time, a double check is required:
static Singleton* getInstance() { if (instance == nullptr) { lock_guard<std::mutex> guard(mtx); if (instance == nullptr) { /* Open up memory construct object Assign value to instance */ instance = new Singleton(); } } return instance; }
At this point, the thread-safe singleton mode is completed.
private: static Singleton *volatile instance; Singleton() { } Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; }; Singleton*volatile Singleton::instance = nullptr;
A more concise way of writing is:
class Singleton { public: static Singleton* getInstance() { //The initialization of the static local variables of the function has automatically added the thread mutual exclusion instruction on the assembly instruction static Singleton instance; return &instance; } private: Singleton() { } Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete; };