Use an easy-to-understand example to illustrate the singleton pattern thoroughly

Keywords: Java

Catalog

1. Background

  • In the enterprise website background system, the website statistics unit will generally be designed independently, such as the number of logins, the number of IP counts and so on.In such processes where global statistics need to be completed, a singleton pattern is used, that is, the entire system only needs one global object to count.
  • In this highly concurrent scenario of website login, this global object is responsible for counting the number of logins, IP, etc. of the current website, which saves resources of the website server and ensures the accuracy of the count.

2. Single Case Mode

1. Concepts

The singleton pattern is one of the most common design patterns and one of the simplest in the overall design pattern.

The singleton pattern ensures that this class has only one instance and instantiates itself and provides this instance to the entire system; this class, also known as singleton class, provides global access.

The singleton pattern has three main points:

  • Privatization of construction methods;
    -- private Singleton() { }
  • The instantiated variable references privatization;
    -- private static final Singleton APP_INSTANCE = new Singleton();
  • There are common ways to get instances
    -- public static SimpleSingleton getInstance() {
    -- return APP_INSTANCE;
    -- }
2. Single implementation of site counting

There are many ways to implement the singleton mode, and here we will only list the three most commonly used implementations. Considering the high concurrency of site logins, we will focus on security issues in a multithreaded environment.

  • Implementation of login thread
    We first create a login thread class to count logins and invoke singleton objects after successful logins.
/**
 * Application of singleton mode--login thread
 *
 * @author zhuhuix
 * @date 2020-06-01
 */
public class Login implements Runnable {
	// logon name
    private String loginName;

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    @Override
    public void run() {
		// TODO 
		// Call singleton object to count after successful login
    }
}

  • Implementation of Main Program
    Write a main program, using multi-threading technology to simulate 10 users concurrent login, complete the login output login count.
/**
 * Singleton Mode--Main Program
 *
 * @author zhuhuix
 * @date 2020-06-01
 */
public class App {
    public final static int num = 10;

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[num];

        for (int i = 0; i < num; i++) {
            Login login = new Login();
            login.setLoginName("" + String.format("%2s", (i + 1)) + "No. user");
            threads[i] = new Thread(login);
            threads[i].start();
        }

        for (int i = 0; i < threads.length; i++) {
            threads[i].join();
        }

		// TODO
		// Call Singleton Object Output Logon Count
}
2.1 Hungry Han Mode
  • Create it at the beginning of the program (no matter 3721, create it first).
  • Natural thread security.
  • This singleton class will exist whether or not it is used in the program.
/**
 * Hungry Han Style Single Case Mode
 *
 * @author zhuhuix
 * @date 2020-06-01
 */
public class SimpleSingleton implements Serializable {
    // Singleton Object
    private static final SimpleSingleton APP_INSTANCE = new SimpleSingleton();
    // Counter
    private AtomicLong count = new AtomicLong(0);

    // Singleton mode must guarantee that the default construction method is a private type
    private SimpleSingleton() {
    }

    public static SimpleSingleton getInstance() {
        return APP_INSTANCE;
    }

    public AtomicLong getCount() {
        return count;
    }

    public void setCount() {
        count.addAndGet(1);
    }

}

We add a single object in Hungry Han mode to the login thread and main program for testing:

/**
 * Application of singleton mode--login thread
 *
 * @author zhuhuix
 * @date 2020-06-01
 */
public class Login implements Runnable {
    // logon name
    private String loginName;

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    @Override
    public void run() {
    	// Hungry Han Style Single Case
        SimpleSingleton simpleSingleton=  SimpleSingleton.getInstance();
        simpleSingleton.setCount();
        System.out.println(getLoginName()+"Login Successful:"+simpleSingleton.toString());
    }

}

/**
 * Singleton Mode--Main Program
 *
 * @author zhuhuix
 * @date 2020-06-01
 */
public class App {
    public final static int num = 10;
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[num];
        for (int i = 0; i < num; i++) {
            Login login = new Login();
            login.setLoginName("" + String.format("%2s", (i + 1)) + "No. user");
            threads[i] = new Thread(login);
            threads[i].start();
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].join();
        }
        System.out.println("Site Common"+SimpleSingleton.getInstance().getCount()+"User logon");

    }
}

The output is as follows:
During the 10 threads'concurrent login process, the same object reference address was obtained, that is, the singleton mode is valid.

2.2 Lazy Mode
  • Definition only at initialization.
  • The instantiation will only be completed if the singleton class is called in the program (I'm lazy because no one touches me).
  • Thread synchronization technology is required to ensure thread safety.

Let's start with an example of not using thread synchronization:

/**
 * Lazy Singleton Mode--No Thread Synchronization Technology Applied
 *
 * @author zhuhuix
 * @date 2020-06-01
 */
public class LazySingleton {
    // Singleton Object
    private static LazySingleton APP_INSTANCE;
    // Counter
    private AtomicLong count = new AtomicLong(0);

    // Singleton mode must guarantee that the default construction method is a private type
    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (APP_INSTANCE == null) {
            APP_INSTANCE = new LazySingleton();
        }
        return APP_INSTANCE;
    }

    public AtomicLong getCount() {
        return count;
    }

    public void setCount() {
        count.addAndGet(1);
    }

  }
/**
 * Application of singleton mode--login thread
 *
 * @author zhuhuix
 * @date 2020-06-01
 */
public class Login implements Runnable {
   
	....
    @Override
    public void run() {
		// Hungry Han Style Single Case
        LazySingleton lazySingleton =LazySingleton.getInstance();
        lazySingleton.setCount();
        System.out.println(getLoginName()+"Login Successful:"+lazySingleton);
    }

}

/**
 * Singleton Mode--Main Program-
 *
 * @author zhuhuix
 * @date 2020-06-01
 */
public class App {
    public final static int num = 10;
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[num];
        for (int i = 0; i < num; i++) {
            Login login = new Login();
            login.setLoginName("" + String.format("%2s", (i + 1)) + "No. user");
            threads[i] = new Thread(login);
            threads[i].start();
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].join();
        }
        System.out.println("Site Common" + LazySingleton.getInstance().getCount() + "User logon");
    }
}

Output results:
During the 10 threads'concurrent login process, four object reference addresses were obtained, and the singleton mode failed.

Analyze the code:

// Thread synchronization not used
public static LazySingleton getInstance() {
		// When multiple threads are concurrent, multiple threads may enter the if statement at the same time, resulting in multiple instances
        if (APP_INSTANCE == null) {
            APP_INSTANCE = new LazySingleton();
        }
        return APP_INSTANCE;
    }

We use thread synchronization to improve the lazy mode:

/**
 * Lazy Singleton Mode
 *
 * @author zhuhuix
 * @date 2020-06-01
 */
public class LazySingleton {
    // Single object, decorated with volatile keyword
    private static volatile LazySingleton APP_INSTANCE;
    // Counter
    private AtomicLong count = new AtomicLong(0);

    // Singleton mode must guarantee that the default construction method is a private type
    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (APP_INSTANCE == null) {
            // Lock classes and double check them
            synchronized (LazySingleton.class) {
                if (APP_INSTANCE == null) {
                    APP_INSTANCE = new LazySingleton();
                }
            }
        }
        return APP_INSTANCE;
    }

    public AtomicLong getCount() {
        return count;
    }

    public void setCount() {
        count.addAndGet(1);
    }

  }

Re-test run:
During the 10 threads'concurrent login process, the same object reference address was obtained, that is, the singleton mode is valid.

2.3 Enumeration classes implement singleton mode

Effective Java recommends the use of enumeration to solve single-case patterns.This approach solves the most important issues; thread security, free serialization, single instance.

/**
 * Use enumeration classes to implement singleton mode
 *
 * @author zhuhuix
 * @date 2020-06-01
 */
public enum EnumSingleton implements Serializable {
    // Singleton Object
    APP_INSTANCE;
    // Counter
    private AtomicLong count = new AtomicLong(0);

    // Singleton mode must guarantee that the default construction method is a private type
    private EnumSingleton() {
    }

    public AtomicLong getCount() {
        return count;
    }

    public void setCount() {
        count.addAndGet(1);
    }
    
}
/**
 * Application of singleton mode--login thread
 *
 * @author zhuhuix
 * @date 2020-06-01
 */
public class Login implements Runnable {
    ...
    @Override
    public void run() {
         EnumSingleton enumSingleton = EnumSingleton.APP_INSTANCE;
         enumSingleton.setCount();
        System.out.println(getLoginName()+"Login Successful:"+enumSingleton.toString());

    }
}

/**
 * Singleton Mode--Main Program
 *
 * @author zhuhuix
 * @date 2020-06-01
 */
public class App {
    public final static int num = 10;
    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[num];
        for (int i = 0; i < num; i++) {
            Login login = new Login();
            login.setLoginName("" + String.format("%2s", (i + 1)) + "No. user");
            threads[i] = new Thread(login);
            threads[i].start();
        }
        for (int i = 0; i < threads.length; i++) {
            threads[i].join();
        }
         System.out.println("Site Common"+EnumSingleton.APP_INSTANCE.getCount()+"User logon");

    }
}

The output is as follows:
This singleton mode is valid during the concurrent login process for 10 threads.

3. Summary

  1. This paper first illustrates the application of the singleton pattern in Web site counting: creating a unique global object to implement the counting of statistical units.
  2. According to this requirement, a Login login thread class and an App main program are established to simulate simultaneous login of multiple users.
  3. There are three different ways to implement the singleton mode, hungry man mode, lazy man mode and enumeration class.
  4. In the process of designing the singleton mode, special attention should be paid to the security of thread synchronization. The practical examples of thread asynchronization are listed in the lazy mode.
  5. Extend your thinking: Why does Effective Java say that the best way to implement the singleton pattern is to enumerate the types of elements?

Posted by BDKR on Mon, 01 Jun 2020 23:05:32 -0700