Java geek


Related reading:

JAVA programming ideas (1) Increase extensibility through dependency injection (2) How to program for interface (3) Remove the ugly if, self-registration strategy mode elegant meet the open and close principle (4) JAVA programming ideas (Builder mode) classical paradigm and factory mode how to choose? Java Programming Ideas (5) Event notification pattern Decoupling process (7) Scenarios using Composition and Inheritance Java Basics (1) Simple, thorough understanding of inner and static inner classes Java basics (2) Memory optimization – Using Java references for caching JAVA foundation (3) ClassLoader implementation hot loading JAVA foundation (4) enumeration (enum) and constant definition, factory class use contrast JAVA foundation (5) functional interface – reuse, The sword of decoupling HikariPool source code (2) Design idea borrowed from JetCache source code (1) beginning JetCache source code (2) Top view people in the workplace (1) IT big factory survival rules


1. Characteristics and usage of singleton pattern

The singleton pattern has only one instance in the same process and is not instantiated multiple times.

Because there is only one instance within the same process and it is not instantiated multiple times, the singleton pattern can be used to cache data and share data within the process.

Based on these two characteristics, the singleton pattern can also be used for decoupling between modules.

2. Singleton mode writing method

The singleton pattern can be written in many different ways, and it is important for application developers to remember just one of them. (Everyone has a limited amount of energy, so when you consider the input/output ratio, it’s not necessary to remember everything.)

// Final class, avoid inheritance (not necessary, usually no one inherits a singleton class even without final modification)
public final class SingletonDemo {
    // Static instance variables; The volatile keyword modifier ensures visibility across threads
    private static volatile SingletonDemo singletonDemo = null;

    // Private constructor to avoid direct external instantiation
    private SingletonDemo(a) {}

    // Static method to get the instance
    public static SingletonDemo getInstance(a) {
        // Make a judgment first, if not empty, return
        if (singletonDemo == null) {
            // Lock on class (process-level lock)
            synchronized (SingletonDemo.class) {
                // Double check that while waiting for the singletonDemo. class lock, another thread may have already initialized it
                if (singletonDemo == null) {
                    singletonDemo = newSingletonDemo(); }}}returnsingletonDemo; }}Copy the code

3. Cache data

There is a lot of static data in the system (data that doesn’t change very often) that can be placed in the cache and accessed from the cache when used instead of accessing the database every time, such as city information, id type, industry, etc., which can be implemented through the singleton pattern.

public final class CertificateTypeMgr {
    private static CertificateTypeMgr certificateTypeMgr;

    private Map<String, CertificateTypeDTO> certificateTypeMap;

    private CertificateTypeMgr(a) {
        init();
    }

    public static CertificateTypeMgr getInstance(a) {
        if (certificateTypeMgr == null) {
            synchronized (CertificateTypeMgr.class) {
                if (certificateTypeMgr == null) {
                    certificateTypeMgr = newCertificateTypeMgr(); }}}return certificateTypeMgr;
    }

    public CertificateTypeDTO getCertificateType(String code) {
        System.out.println("getCertificateType");
        return certificateTypeMap.get(code);
    }

    private void init(a) {
        System.out.println("CertificateTypeMgr init....");
        certificateTypeMap = new ConcurrentHashMap<>();
        // Initialize data from the database....
        certificateTypeMap.put("101".new CertificateTypeDTO("101"."ID Card"));
        certificateTypeMap.put("102".new CertificateTypeDTO("102"."Household Register"));
        certificateTypeMap.put("102".new CertificateTypeDTO("103"."Student Card"));
        System.out.println("CertificateTypeMgr init end."); }}public class CertificateTypeDTO {
    // m indicates a member variable
    public String mCode;
    public String mName;

    public CertificateTypeDTO(String code, String name) {
        this.mCode = code;
        this.mName = name;
    }

    @Override
    public String toString(a) {
        StringBuilder builder = new StringBuilder();
        builder.append("CertificateTypeDTO {").append("code=").append(mCode).append(",")
                .append("name=").append(mName).append("}");
        returnbuilder.toString(); }}public class EntryDemo {
    public static void main(String[] args) {
        // Usually the cache is initialized at application startup (diligent loading), simulating startup loading, in a different thread
        new Thread() {
            @Override
            public void run(a) {
                CertificateTypeMgr.getInstance();
            }
        }.start();

        // Use cache
        CertificateTypeDTO certificateTypeDTO = CertificateTypeMgr.getInstance().getCertificateType("102"); System.out.println(certificateTypeDTO.toString()); }}Copy the code

Output:

CertificateTypeMgr init....
CertificateTypeMgr init end.
getCertificateType, code=102
CertificateTypeDTO {code=103, name=Student Card}
Copy the code

As you can see from the above access, in-process data sharing is implemented by holding non-static member variables for static object instances (certificateTypeMgr).

You don’t have to use singletons. You can also use static methods to access static variables. Try refactoring as follows:

public class StaticCertificateTypeMgr {

    Static methods can only access static variables and require static decorations
    private static Map<String, CertificateTypeDTO> certificateTypeMap;

    private static volatile boolean isInited;

    public static CertificateTypeDTO getCertificateType(String code) {
        System.out.println("getCertificateType, code=" + code);
        // Return only after initialization, otherwise null-pointer exception will be thrown
        if (isInited) {
            return certificateTypeMap.get(code);
        }
        return null;
    }

    public static void init(a) {
        System.out.println("CertificateTypeMgr init....");
        certificateTypeMap = new ConcurrentHashMap<>();
        // Initialize data from the database....
        certificateTypeMap.put("101".new CertificateTypeDTO("101"."ID Card"));
        certificateTypeMap.put("102".new CertificateTypeDTO("102"."Household Register"));
        certificateTypeMap.put("102".new CertificateTypeDTO("103"."Student Card"));
        System.out.println("CertificateTypeMgr init end.");
        isInited = true; }}public class StaticEntryDemo {
    public static void main(String[] args) {
        // Usually the cache is initialized at application startup (diligent loading), simulating startup loading, in a different thread
        new Thread() {
            @Override
            public void run(a) { StaticCertificateTypeMgr.init(); } }.start(); ! [](https://user-gold-cdn.xitu.io/2020/6/7/1728ef7878cf0a8e? w=1039&h=364&f=png&s=24753)
        // Use cache
        CertificateTypeDTO certificateTypeDTO = StaticCertificateTypeMgr.getCertificateType("102"); System.out.println(certificateTypeDTO); }}Copy the code

Output:

getCertificateType, code=102
CertificateTypeMgr init....
null
CertificateTypeMgr init end.
Copy the code

It can be seen that the static method will not wait for the completion of cache initialization to obtain the cache data, and the data may not be obtained. However, the singleton mode can ensure that the cache has been initialized when the data is obtained, and the data can be obtained.

Therefore, while the singleton pattern is essentially an in-process data sharing through static instances, it is more usable than directly using static methods to obtain static variable data.

4. Used for intra-process data sharing

An example of in-process data sharing is the sharing of user session information, such as login ID, IP address, etc., which is used for authentication before each service invocation and can only be invoked after the authentication succeeds.

Its usage is similar to that of caching and is no longer described.

5. Decoupling between modules

The relationship between modules should be one-way dependence, avoiding bidirectional dependence. If bidirectional dependence occurs, one of the dependencies should be removed to form a common module, as follows:

Refactoring:

After the refactoring:

In addition to this approach, the event notification mode in the previous chapter can also be used for module decoupling:

When the Listener sends out an Event, if the SubscriberImpl is directly called, module A and module B will be interdependent. However, the EventRegistry singleton can avoid bidirectional dependency. The calling process is as follows:

1. Module B registers SubscriberImpl through EventRegistry

2. The Listener iterates through the EventRegistry and dispatches events through the Dispatcher.

In this way, module A does not depend on module B, avoiding bidirectional dependencies.

6. Summary

1. The singleton mode can be used to cache and share data within a process.


2. In event notification mode, singleton mode can be used for decoupling between modules.


end.


<– Read the mark, left like!