C Design Patterns Series Singleton Patterns

  • 2021-10-15 11:13:39
  • OfStack

1. Description:

Guarantee that a class has only one instance and provide a global access point to access it.

2. The singleton pattern has three main characteristics:

2.1 The singleton class ensures that it has only one instance.
2.2 Singleton classes must create their own instances.
2.3 Singleton classes must provide only 1 instances of other objects.

3. Implementation: Lazy man singleton class and hungry man singleton class

3.1 Lazy singleton class
For the lazy pattern, we can understand this: the singleton is very lazy, only acts when it needs to, and never knows how to prepare early. When it needs an object, it judges whether there is an object. If there is no object, it immediately creates an object and then returns. If there is an object, it will no longer be created and return immediately.
Lazy mode is created only when an external object requests an instance for the first time.

3.2 Hungry Han Single Case
For the Hunger pattern, we can understand this: The singleton class is very hungry and desperate to eat, so it creates objects as soon as the class loads.

3.3 Advantages and disadvantages of lazy man mode and hungry man mode:
Lazy mode, which is characterized by slow runtime to get objects, but faster to load classes. It only takes up resources for 1 part of the whole application life cycle.
Hungry mode, which is characterized by slow loading of classes, but faster acquisition of objects at runtime. It takes up resources from loading to the end of application.
There is not much performance difference between these two modes for lightweight objects that initialize quickly and occupy less resources, and there is no problem in choosing lazy or hungry objects. But for the slow initialization, take up a lot of resources for the heavyweight object, there will be a more obvious difference. Therefore, when applying Hungry Mode to heavyweight objects, the class is slow to load, but fast to run; Lazy mode, on the other hand, is fast when the class loads, but slow when the runtime gets the object for the first time.
From the perspective of user experience, we should choose Hungry Han mode first. We are willing to wait for a program to take a long time to initialize, but we don't like to wait too long when the program runs, which gives people a feeling of slow response. Therefore, for singleton mode with heavyweight objects involved, we recommend hungry mode.
For lightweight objects that initialize quickly, either method can be selected. If a large number of singleton patterns are used in an application, we should weigh the two methods. The single instance of lightweight object adopts lazy mode, which reduces the burden of loading, shortens the loading time and improves the loading efficiency; At the same time, because they are lightweight objects, the creation of these objects is carried out when they are used, which actually allocates the time consumed by creating singleton objects to the whole application, and has no great influence on the running efficiency of the whole application.

4. Code implementation:

4.1 Lazy Style


  public class Singleton
  {
    private static Singleton m_Instance;

    private Singleton()
    {
      //  Define the default constructor as private, preventing it from being called externally to instantiate other objects 
    }

    public static Singleton GetInstance()
    {

      if (m_Instance == null)
      {
        m_Instance = new Singleton();
      }

      return m_Instance;
    }
  }

4.2 Hungry Han style


  //  Defined as sealed Prevent derivation, because derivation may increase instances 
  public sealed class Singleton
  {
    private static readonly Singleton m_Instance = new Singleton();
    private Singleton()
    {
      //  Define the default constructor as private, preventing it from being called externally to instantiate other objects 
    }

    public static Singleton GetInstance()
    {
      return m_Instance;
    }
  }

5. Summary of Models

5.1 Advantages:
Prevents multiple objects from being instantiated in your application. This saves overhead, each instance takes up 1 fixed amount of memory, and it takes time and space to create objects.

5.2 Disadvantages:

5.3 Applicable occasions:
5.3. 1 Control the use of resources, and control the concurrent access of resources through thread synchronization;
5.3. 2 Control the number of instances generated to achieve the purpose of saving resources.
5.3. 3 Used as a communication medium, that is, data sharing, it can communicate between two unrelated threads or processes without establishing direct association.

5.4 Support for design principles:

The core point of using singleton pattern is that it embodies the principle of "single 1 responsibility" in object-oriented encapsulation.

6. Add: In the process of multithreaded opening, it is possible to prevent two threads from instantiating objects at the same time when using lazy singleton mode.

The solution is given below

6.1 Using the locking mechanism


  public class Singleton
  {
    private static Singleton m_Instance;

    static readonly object o = new object();

    private Singleton()
    {
      //  Define the default constructor as private, preventing it from being called externally to instantiate other objects 
    }

    public static Singleton GetInstance()
    {
      lock (o)
      {
        if (m_Instance == null)
        {
          m_Instance = new Singleton();
        }
      }

      return m_Instance;
    }
  }

Using a locking mechanism prevents two threads from creating objects at the same time, but there is a performance problem here. Whenever one thread accesses GetInstance (), it is unnecessary to lock it.

6.2 Double Locking


  public class Singleton
  {
    private static Singleton m_Instance;

    static readonly object o = new object();

    private Singleton()
    {
      //  Define the default constructor as private, preventing it from being called externally to instantiate other objects 
    }

    public static Singleton GetInstance()
    {
      //  Added here 1 To determine whether an instance exists, lock it only if it does not exist, that is, only add it in the life cycle of this instance 1 Secondary lock 
      if (m_Instance == null)
      {
        lock (o)
        {
          if (m_Instance == null)
          {
            m_Instance = new Singleton();
          }
        }
      }

      return m_Instance;
    }
  }

Double locking ensures that an instance is locked only once in its life cycle, so it has no impact on performance.


Related articles: