2.1 Modal Motivation

For some classes in the system, only one instance is important. For example, there can be multiple printing tasks in a system, but only one working task. A system can have only one window manager or file system; A system can only have one timing tool or ID generator.

How do you ensure that there is only one instance of a class that is easily accessed? Defining a global variable ensures that an object is always accessible, but does not prevent us from instantiating multiple objects.

A better solution is to make the class itself responsible for keeping its unique instance. This class guarantees that no other instances will be created, and it can provide a method to access that instance. This is the pattern motivation for the singleton pattern.

2.2 Definition and characteristics of the mode

Singleton Pattern: The Singleton Pattern ensures that a class has only one instance and that it instantiates and provides that instance to the entire system. This class is called a Singleton class, which provides globally accessible methods. For example, only one task manager can be opened in Windows, so as to avoid the waste of memory resources caused by opening multiple task manager Windows, or the inconsistent display content of each window and other errors. The singleton pattern is an object creation pattern. Singleton is also called singleton or singleton. In a computer system, And Windows in the recycle bin, operating systems, file systems, the threads in a multi-threaded pool, graphics driver object, printer background processing services, the application log object, database connection pool, counter website, Web application configuration object, dialog box, and the system cache in the application program is often designed to be a singleton.

The singleton pattern has three characteristics:

  • A singleton class has only one instance object;
  • The singleton object must be created by the singleton class itself;
  • A singleton class provides a global access point to that singleton.

 advantages

  • Provides controlled access to a unique instance. Because the singleton class encapsulates its unique instance, it has tight control over how and when customers access it and provides shared concepts for design and development teams.
  • Since only one object exists in system memory, system resources are saved, and the singleton pattern can definitely improve system performance for objects that need to be created and destroyed frequently.
  • A variable number of instances is allowed. We can extend it based on the singleton pattern, using a method similar to singleton control to obtain a specified number of object instances.

 shortcomings

  • Because there is no layer of abstraction in the singleton pattern, it is difficult to extend the singleton class.
  • Singleton classes are too heavy on responsibilities and violate the “single responsibility principle” to some extent. Because the singleton class acts both as a factory, providing factory methods, and as a product, containing business methods that blend the creation of the product with the functionality of the product itself.
  • Abuse of singletons will bring some negative problems, for example, in order to save resources, the database connection pool object is designed as a singleton class, which may lead to too many programs sharing the connection pool object and connection pool overflow. Many object-oriented languages (such as Java and C#) now provide automatic garbage collection. Therefore, if an instantiated object is not used for a long time, the system will consider it garbage, destroy and recycle the resource, and re-instantiate the object the next time it is used, which will result in the loss of the object state.

2.3 Structure and implementation of the mode

2.3.1 Pattern structure

The singleton pattern is one of the simplest design patterns. In general, the constructors of ordinary classes are public, and external classes can generate multiple instances through the “new constructor ()”. However, if you make the constructor of a class private, the external class cannot call that constructor and therefore cannot generate multiple instances. The class itself must define a static private instance and provide a static public function to create or get the static private instance.

The main roles of the singleton pattern are as follows.

  • Singleton class: a class that contains an instance and can create its own instance.
  • Access classes: Classes that use singletons.

Its structure is shown in Figure 1.

Figure 1 structure diagram of the singleton pattern

2.3.2 Implementation of the pattern

 Singelton. H

#ifndef _SINGELTON_H_
#define _SINGELTON_H_

#include <iostream>

using namespace std;

// Thread unsafe class
class Singelton
{
private:
	Singelton() {}
	static Singelton * m_singel;
public:
	static Singelton * getInstance(a)
	{
		if (nullptr == m_singel)
		{
			cout << "Create instance" << endl;
			m_singel = new Singelton(a); }return m_singel;
	}
static void DestoryInstance(a)
    {
        if(m_singel ! =NULL )
        {
            delete m_singel;
            m_singel = NULL; }}};#endif 
Copy the code

 main. CPP

/**Includes*********************************************************************/
#include <iostream>
#include "Singelton.h"

/**namespace********************************************************************/
using namespace std;

Singelton * Singelton::m_singel = nullptr;

/** * @brief main function * @param argc argv * @retval None */
int main(int argc, char *argv[])
{
    // Singleton mode
	Singelton * p1 = Singelton::getInstance(a); Singelton * p2 = Singelton::getInstance(a);if (p1 == p2)
		cout << "Agreement" << endl;
	else
		cout << "Inconsistent" << endl;
	Singelton::DestoryInstance(a);return 0;
}
Copy the code

The result is as follows:

The above code is a security risk if the class is used in multithreading. It is possible to create two instances of the class when multiple threads are accessing the instance for the first time, thus causing a memory leak. And the two instances are not the same, as follows:

/**Includes*********************************************************************/
#include <iostream>
#include <thread>
#include "Singelton.h"

#define WIN32

#ifdef WIN32
	#include <Windows.h>
#else
	#include <unistd.h>
#endif

/**namespace********************************************************************/
using namespace std;

Singelton * Singelton::m_singel = nullptr;

void func1(a)
{
#ifdef WIN32
	Sleep(1);
#else
	sleep(1);
#endif

    Singelton * p1 = Singelton::getInstance(a); cout <<"func1 thread:" << p1 << endl;
}

void func2(a)
{
#ifdef WIN32
	Sleep(1);
#else
	sleep(1);
#endif

	Singelton * p2 = Singelton::getInstance(a); cout <<"func2 thread:" << p2 << endl;
}

/** * @brief main function * @param argc argv * @retval None */
int main(int argc, char *argv[])
{
    // Singleton mode
	thread thread1(func1).thread2(func2);
	thread1.detach(a); thread2.detach(a); cout <<"Main thread" << endl;

#ifdef WIN32
	Sleep(2);
#else
	sleep(2);
#endif
	Singelton::DestoryInstance(a);return 0;
}
Copy the code

The result is as follows:



G++ main. CPP -pthread-std =c++11

As you can see from the above results, the two threads do not get the same instance when they create two instances. This is because both threads are running at the same time, and when getInstance() is called, the instance is not created, so both threads go into the code block that creates the instance, and both instances are created. The first instance that was created was not released after it was replaced. This is a memory leak, and the thread that created the instance was called first and got the wrong instance, which can cause logic errors.

The solution is either to create an instance of the class before any code that accesses the instance executes, or to add thread synchronization to the code that accesses the instance.

 method a

/**Includes*********************************************************************/
#include <iostream>
#include <thread>
#include "Singelton.h"

#define WIN32

#ifdef WIN32
	#include <Windows.h>
#else
	#include <unistd.h>
#endif

/**namespace********************************************************************/
using namespace std;

Singelton * Singelton::m_singel = nullptr;

void func1(a)
{
#ifdef WIN32
	Sleep(1);
#else
	sleep(1);
#endif

    Singelton * p1 = Singelton::getInstance(a); cout <<"func1 thread:" << p1 << endl;
}

void func2(a)
{
#ifdef WIN32
	Sleep(1);
#else
	sleep(1);
#endif

	Singelton * p2 = Singelton::getInstance(a); cout <<"func2 thread:" << p2 << endl;
}

/** * @brief main function * @param argc argv * @retval None */
int main(int argc, char *argv[])
{
    // Singleton mode
    // Get an instance before starting the thread.
	Singelton::getInstance(a);thread thread1(func1).thread2(func2);
	thread1.detach(a); thread2.detach(a); cout <<"Main thread" << endl;

#ifdef WIN32
	Sleep(2);
#else
	sleep(2);
#endif
	Singelton::DestoryInstance(a);return 0;
}
Copy the code

The result is as follows:

 method 2

#ifndef _SINGELTON_H_
#define _SINGELTON_H_

#include <iostream>
#include <mutex>

using namespace std;

class Singelton
{
private:
	Singelton() {}
	static Singelton * m_singel;
	static mutex m_mutex;/ / thread lock
public:
	static Singelton * getInstance(a)
	{
		if (nullptr == m_singel)
		{
		    m_mutex.lock(a);if(nullptr == m_singel)
            {
                cout << "Create instance" << endl;
                m_singel = new Singelton(a); } m_mutex.unlock(a); }return m_singel;
	}
	static void DestoryInstance(a)
    {
        if(m_singel ! =NULL )
        {
            delete m_singel;
            m_singel = NULL; }}};#endif
Copy the code

 main. CPP

/**Includes*********************************************************************/
#include <iostream>
#include <thread>
#include "Singelton.h"

#define WIN32

#ifdef WIN32
	#include <Windows.h>
#else
	#include <unistd.h>
#endif

/**namespace********************************************************************/
using namespace std;

Singelton * Singelton::m_singel = nullptr;
mutex Singelton::m_mutex;

void func1(a)
{
#ifdef WIN32
	Sleep(1);
#else
	sleep(1);
#endif

    Singelton * p1 = Singelton::getInstance(a); cout <<"func1 thread:" << p1 << endl;
}

void func2(a)
{
#ifdef WIN32
	Sleep(1);
#else
	sleep(1);
#endif

	Singelton * p2 = Singelton::getInstance(a); cout <<"func2 thread:" << p2 << endl;
}

/** * @brief main function * @param argc argv * @retval None */
int main(int argc, char *argv[])
{
    // Singleton mode
    // Get an instance before starting the thread.
	thread thread1(func1).thread2(func2);
	thread1.detach(a); thread2.detach(a); cout <<"Main thread" << endl;

#ifdef WIN32
	Sleep(2);
#else
	sleep(2);
#endif
	Singelton::DestoryInstance(a);return 0;
}
Copy the code

The result is as follows:

M_singel == NULL is checked twice, which is a reference to the so-called “double lock” mechanism used in Java’s singleton implementation. Because it needs to pay the corresponding price to lock and unlock once, it can avoid multiple lock and unlock operations by judging twice, and at the same time ensure thread safety. However, this implementation method in the peacetime project development used well, there is no problem? However, locking can be a performance bottleneck if large data operations are performed.

In summary, both approaches have their own advantages. The first approach is simple to implement, but less secure in complex systems. The method 2 implementation is slightly more complex and requires a lock(mutex) every time an instance is accessed, which is more expensive. But method two is safe in any case. In a word, method 1 is suitable for use when the system is not too complex, and method 2 is suitable for use when the system is more complex.

2.4 Application Scenarios of Mode

A table with auto-numbered primary keys can be used by multiple users at the same time, but only one place in the database can be assigned the next primary key number, otherwise primary key duplication will occur, so the primary key number generator must be unique, which can be implemented through the singleton pattern.

In application scenarios, a class requires only one object to be generated, such as the monitor of a class, the ID number of each person, and so on.

When objects need to be shared. Because the singleton pattern allows you to create only one object, sharing that object saves memory and speeds up object access. For example, configuration objects on the Web and connection pools of databases.

When a class needs frequent instantiation and the objects created are frequently destroyed, such as a multithreaded thread pool, network connection pool, etc.

2.5 Schema extensions

The singleton mode can be extended to the finite Multitcm mode. This mode can generate a limited number of instances and save them in ArmyList, which can be randomly obtained when customers need. Its structure diagram is shown in Figure 2.

FIG. 2 Structure diagram of a finite multi-case pattern

2.6 summarize

The singleton pattern ensures that a class has only one instance and that it instantiates and makes that instance available to the entire system. This class is called a singleton class, which provides globally accessible methods. The singleton pattern has three main points: first, there can be only one instance of a class; Second, it must create the instance itself; Third, it must provide this instance to the entire system itself. The singleton pattern is an object creation pattern.

The singleton pattern contains only one singleton role: an internal implementation of a singleton class generates only one instance, and it provides a static factory method that allows customers to use its unique instance; To prevent instantiation externally, its constructor is designed to be private.

The purpose of the singleton pattern is to ensure that a class has only one instance and to provide a global access point to it. The singleton class has a private constructor that ensures that users cannot instantiate it directly with the new keyword. In addition, the pattern contains a static private member variable and a static public factory method. The factory method is responsible for verifying the existence of the instance and instantiating itself, then storing it in static member variables to ensure that only one instance is created.

The main advantages of the singleton pattern are that it provides controlled access to a unique instance and saves system resources. Its main disadvantages are that it is difficult to extend due to the lack of an abstraction layer and the singleton class is too responsible.

The singleton pattern applies when the system only needs one instance object; Only one common access point is allowed for a single instance of a client invoking class.