Implementation of special singleton in singleton mode

Uniqueness in singleton mode

Definition of singleton: "a class can only create a unique object (or instance), and that class is a singleton class. This design pattern is called singleton design pattern, or singleton pattern for short."

Common examples include thread singletons, process singletons (generally implemented by default), and multi process singletons.

 

Implement thread unique singleton

"Process uniqueness" refers to the uniqueness within processes, but not between processes. By analogy, "thread uniqueness" refers to the uniqueness within a thread, but not between threads.

In fact, "process uniqueness" also means that it is unique within and between threads, which is the difference between "process uniqueness" and "thread uniqueness".

In the code, we use a HashMap to store objects, 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 unique singletons. However, the underlying implementation principle of ThreadLocal is also based on the HashMap shown in the following code.

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();
  }
}

 

Implement single instance in multi process (cluster) environment

"Process uniqueness" means that it is unique within processes and not unique between processes. "Thread unique" means unique within threads and not unique between threads. A cluster is equivalent to a collection composed of multiple processes. "Cluster uniqueness" means that it is unique within processes and between processes. That is, different processes share the same object, and cannot create multiple objects of the same class.

The classic singleton mode is unique within a process. How to implement a singleton that is also unique between processes? If the same object is shared between different processes, it will be difficult to implement the only singleton in the cluster. Specifically, we need to serialize the singleton object and store it in an external shared storage area (such as a file). When a process uses this singleton object, it needs to read it from the external shared storage area into memory, deserialize it into an object, and then use it. After using it, it needs to store it back to the external shared storage area. In order to ensure that only one object exists between processes at any time, a process needs to lock the object after acquiring it to prevent other processes from acquiring it again. After the process uses the object, it also needs to explicitly delete the object from memory and release the lock on the object.

public class IdGenerator {
  private AtomicLong id = new AtomicLong(0);
  private static IdGenerator instance;
  private static SharedObjectStorage storage = FileSharedObjectStorage(/*Input parameter omission, such as file address*/);
  private static DistributedLock lock = new DistributedLock();
  
  private IdGenerator() {}

  public synchronized static IdGenerator getInstance() 
    if (instance == null) {
      lock.lock();
      instance = storage.load(IdGenerator.class);
    }
    return instance;
  }
  
  public synchroinzed void freeInstance() {
    storage.save(this, IdGeneator.class);
    instance = null; //Release object
    lock.unlock();
  }
  
  public long getId() { 
    return id.incrementAndGet();
  }
}

// IdGenerator Use examples
IdGenerator idGeneator = IdGenerator.getInstance();
long id = idGenerator.getId();
IdGenerator.freeInstance();

Implement a multi instance mode

"Singleton" means that a class can only create one object. Correspondingly, "multiple instances" means that a class can create multiple objects, but the number is limited. For example, only three objects can be created. Simple examples in code

public class BackendServer {
  private long serverNo;
  private String serverAddress;

  private static final int SERVER_COUNT = 3;
  private static final Map<Long, BackendServer> serverInstances = new HashMap<>();

  static {
    serverInstances.put(1L, new BackendServer(1L, "192.134.22.138:8080"));
    serverInstances.put(2L, new BackendServer(2L, "192.134.22.139:8080"));
    serverInstances.put(3L, new BackendServer(3L, "192.134.22.140:8080"));
  }

  private BackendServer(long serverNo, String serverAddress) {
    this.serverNo = serverNo;
    this.serverAddress = serverAddress;
  }

  public BackendServer getInstance(long serverNo) {
    return serverInstances.get(serverNo);
  }

  public BackendServer getRandomInstance() {
    Random r = new Random();
    int no = r.nextInt(SERVER_COUNT)+1;
    return serverInstances.get(no);
  }
}

In fact, there is another way to understand the multi instance mode: only one object can be created for the same type, and multiple objects can be created for different types. How to understand the "type" here?

The specific code is as follows. In the code, the logger name is the "type" just mentioned. The object instances obtained by the same logger name are the same, and the object instances obtained by different logger names are different.

public class Logger {
  private static final ConcurrentHashMap<String, Logger> instances
          = new ConcurrentHashMap<>();

  private Logger() {}

  public static Logger getInstance(String loggerName) {
    instances.putIfAbsent(loggerName, new Logger());
    return instances.get(loggerName);
  }

  public void log() {
    //...
  }
}

//l1==l2, l1!=l3
Logger l1 = Logger.getInstance("User.class");
Logger l2 = Logger.getInstance("User.class");
Logger l3 = Logger.getInstance("Order.class");

This multi instance mode is understood in a way similar to the factory mode. The difference between the factory pattern and the multi instance pattern is that the objects created by the multi instance pattern are objects of the same class, while the factory pattern creates objects of different subclasses. This will be discussed in the next lesson. In fact, it is somewhat similar to the sharing mode. The difference between the two will be analyzed when we talk about the sharing mode. In addition, in fact, enumeration types are also equivalent to multi instance mode. A type can only correspond to one object, and a class can create multiple objects.

 

The scope of singleton class object uniqueness is not the process, but the Class Loader.

classloader has two functions: 1 Used to load the class file into the JVM; 2. confirm which class loader each class should be loaded by, and it is also used to judge whether the two classes in the JVM runtime are equal.
The principle of the parent delegation model is that when a class loader receives a class loading request, it will first request its parent class loader to load. This is the case at each layer. When the parent class loader cannot find the class (according to the fully qualified name of the class), the child class loader will try to load it by itself.
So the parental delegation model solves the problem of class repeated loading. For example, if the user writes a fully qualified java Lang.Object and use its own class loader to load it. At the same time, BootstrapClassLoader loads the java Lang.Object, so there are two object classes in the memory. At this time, many problems will occur. For example, you cannot locate a specific class according to the fully qualified name. With the two parent delegation model, all class loading operations will be delegated to the parent class loader first. In this way, even if the user has customized a java Lang.Object, but since the BootstrapClassLoader has detected that it has loaded this class, the user-defined class loader will not be loaded again. Therefore, the parental delegation model can guarantee the uniqueness of the class in memory.
In connection with the problem after class, the user defined the singleton class, so that the JDK will not be loaded repeatedly after loading once using the two parent delegation model, ensuring the uniqueness of the singleton class in the process, which can also be considered as the uniqueness in the classloader. Of course, if there is no parental delegation model, multiple classloaders will have multiple instances, and uniqueness cannot be guaranteed.

Posted by libertyct on Tue, 31 May 2022 13:24:55 +0530