Singleton mode of design mode

Posted by OsvaldoM on Wed, 26 Jan 2022 12:12:32 +0100

The singleton design pattern is very simple to understand. A class can only create one object (or instance), and this class is a singleton class. This design pattern is called singleton pattern.

Usage scenario

Handling resource access conflicts

In the following example, if each class creates a Logger instance, the log content may be overwritten.

public class Logger {
  private FileWriter writer;

  public Logger() {
    File file = new File("log.txt");
    writer = new FileWriter(file, true); //true indicates additional write
  }

  public void log(String message) {
    writer.write(mesasge);
  }
}


public class UserController {
  private Logger logger = new Logger();

  public void login(String username, String password) {
    // ... Omit business logic code
    logger.log(username + " logined!");
  }
}

public class OrderController {
  private Logger logger = new Logger();

  public void create(OrderVo order) {
    // ... Omit business logic code
    logger.log("Created an order: " + order.toString());
  }
}

Represents a globally unique class

If only one copy of some data should be saved in the system, it is more suitable to be designed as a singleton class. For example, configuration information class, global ID generator, etc.

How to implement a singleton?

To implement a singleton, we should consider the following points:

  • The constructor needs to have private access rights, so as to avoid external instances created through new;
  • Consider thread safety when creating objects;
  • Consider whether to support delayed loading;
  • Consider whether getInstance() has high performance (whether it is locked).

Hungry Han style

public class Singleton {
  private static final Singleton instance = new Singleton();

  private Singleton() {}

  public static Singleton getInstance() {
    return instance;
  }
}

Lazy style

The advantage of lazy type over hungry type is to support delayed loading. But the disadvantage is also obvious, because the synchronized keyword is used, resulting in low concurrency of this method. If this singleton class is occasionally used, this implementation is acceptable. However, if it is used frequently, it will lead to performance bottlenecks, which is not desirable.

public class Singleton {
  private static Singleton instance;

  private Singleton() {}

  public static synchronized Singleton getInstance() {
    if (instance == null) {
      instance = new Singleton();
    }
    return instance;
  }
}

Double detection

This is a singleton implementation that supports both delayed loading and high concurrency.

public class Singleton {
  private static Singleton instance;

  private Singleton() {}

  public static Singleton getInstance() {
    if (instance == null) {
      synchronized(Singleton.class) { // This is a class level lock
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance;
  }
}

In Java 1 5. The following instance = new Singleton(); There is an instruction rearrangement problem. You need to add the volatile keyword to the instance member variable, java1 There will be no problem with this after 5.

Static inner class

This method makes use of Java's static internal classes, which is a bit similar to hungry Chinese style, but it can delay loading.

When the external class Singleton is loaded, the SingletonHolder instance object is not created. SingletonHolder will be loaded only when getInstance() method is called, and instance will be created at this time. The uniqueness of insance and the thread safety of the creation process are guaranteed by the JVM. Therefore, this implementation method can not only ensure thread safety, but also delay loading.

public class Singleton {
  private Singleton() {}

  private static class SingletonHolder{
    private static final Singleton instance = new Singleton();
  }

  public static Singleton getInstance() {
    return SingletonHolder.instance;
  }
}

enumeration

This is the simplest implementation, which is based on the singleton implementation of enumeration types. This implementation ensures the thread safety of instance creation and the uniqueness of the instance through the characteristics of Java enumeration type itself.

public enum IdGenerator {
  INSTANCE;
  private AtomicLong id = new AtomicLong(0);

  public long getId() {
    return id.incrementAndGet();
  }
}

How to implement thread unique singleton?

The above singleton class object is unique to a process. A process can only have one singleton object. How to implement a single instance of a thread?

Suppose IdGenerator is the only singleton class of a thread. Within thread a, we can create a singleton object a. Because it is unique within a thread, a new IdGenerator object cannot be created within thread a, and it can not be unique between threads. Therefore, in another thread b, we can re create a new singleton object b.

We store objects through a ConcurrentHashMap, where key is the thread ID and value is the object. In this way, different threads correspond to different objects, and the same thread can only correspond to one object. In fact, the Java language itself provides the ThreadLocal tool class, which makes it easier to implement thread singleton.

public class IdGenerator {
  private AtomicLong id = new AtomicLong(0);

  private static final ConcurrentHashMap<Long, IdGenerator> instances
          = new ConcurrentHashMap<>();

  private IdGenerator() {}

  public static IdGenerator getInstance() {
    Long currentThreadId = Thread.currentThread().getId();
    instances.putIfAbsent(currentThreadId, new IdGenerator());
    return instances.get(currentThreadId);
  }

  public long getId() {
    return id.incrementAndGet();
  }
}

Topics: Design Pattern