Talk about the singleton design pattern in Java programming

  • 2020-04-01 04:07:26
  • OfStack

The print log function is often used when writing software to help you debug and locate problems, and to help you analyze data once the project is launched. But the system.out.println () method that comes with Java is rarely used in real project development, and even code checking tools like findbugs consider using system.out.println () to be a bug.

Why should system.out.println (), a Java novice artifact, be spurned in real project development? In fact, as long as the detailed analysis, you will find it a lot of shortcomings. For example, uncontrollable, all the logs will be printed as usual after the project is launched, thus reducing the operation efficiency; Or the log cannot be recorded to a local file, and once the print is cleared, the log will never be found again. Or print without a Tag, and you won't be able to tell which class the log line was printed in.

Your leader is no fool, and he knows all about the drawbacks of using system.out.println (), so his task today is to make a logging tool class to provide better logging. However, your leader is not good, and does not let you start off with an awesome logging tool with all the features, just a logging tool that can control the printing level.

This requirement wasn't too hard for you, so you started writing it right away and finished the first version very quickly:


  public class LogUtil { 
    public final int DEBUG = 0; 
   
    public final int INFO = 1; 
   
    public final int ERROR = 2; 
   
    public final int NOTHING = 3; 
   
    public int level = DEBUG; 
   
    public void debug(String msg) { 
      if (DEBUG >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public void info(String msg) { 
      if (INFO >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public void error(String msg) { 
      if (ERROR >= level) { 
        System.out.println(msg); 
      } 
    } 
  } 



With this class to print the log, you are free to control what is printed simply by controlling the level of the level. For example, now that the project is under development, set the level to DEBUG so that all the log information is printed. If the project is online, you can set the level to INFO, so that you can only see log printing at INFO and above. If you only want to see the ERROR log, you can set the level to ERROR. If your project is a client version and you don't want any logs to be printed, set the level to NOTHING. To print, just call:


  new LogUtil().debug("Hello World!"); 



You can't wait to introduce this tool to your leader. After hearing your introduction, your leader says: "good for you, everyone will use this tool you wrote to print logs in the future!"

But before long, your leader found you to give feedback. He said that although this tool is good, but printing this kind of thing is not to distinguish objects, here every time you need to print a log need to new a new LogUtil, too much memory, I hope you can change this tool to use singleton mode implementation.

You think your leader has a point, and you're taking the opportunity to practice using design patterns, so you write the following code (ps: I implemented the code myself, and I didn't really pay attention to thread synchronization at first) :


  public class LogUtil { 
    private static LogUtil logUtilInstance; 
   
    public final int DEBUG = 0; 
   
    public final int INFO = 1; 
   
    public final int ERROR = 2; 
   
    public final int NOTHING = 3; 
   
    public int level = DEBUG; 
   
    private LogUtil() { 
   
    } 
   
    public static LogUtil getInstance() { 
      if (logUtilInstance == null) { 
        logUtilInstance = new LogUtil(); 
      } 
   
      return logUtilInstance; 
    } 
   
    public void debug(String msg) { 
      if (DEBUG >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public void info(String msg) { 
      if (INFO >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public void error(String msg) { 
      if (ERROR >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public static void main(String[] args) { 
      LogUtil.getInstance().debug("Hello World!"); 
    } 
  } 


Start by privatizing LogUtil's constructor so that you can't create an instance of LogUtil using the new keyword. Then use a private sLogUtil static variable to hold the instance, and provide a public getInstance method to get the instance of LogUtil, in which if the sLogUtil is empty, new a new LogUtil instance, otherwise return sLogUtil directly. This ensures that there is only one instance of LogUtil in memory. Singleton mode done! At this time, the code to print the log needs to be changed as follows:


  LogUtil.getInstance().debug("Hello World");  


You show the version to your leader, who smiles and says, "this seems to implement singletons, but there are bugs.
You wonder, isn't that how singletons are always implemented? What other bugs could there be?

Your leader prompts you to use singleton mode so that the class can only have one instance in memory, but do you consider printing logs in multiple threads? As shown in the following code:


  public static LogUtil getInstance() { 
    if (logUtilInstance == null) { 
      logUtilInstance = new LogUtil(); 
    } 
   
    return logUtilInstance; 
  } 


If there are two threads executing the getInstance method at the same time, the first thread has just executed line 2, but has not yet executed line 3. At this time, the second thread will execute line 2, and it will find that sLogUtil is still null, so it will enter the if judgment. Your singleton pattern fails because two different instances are created.
You suddenly understand, but your thinking is very fast, immediately came up with a solution, just need to add a synchronization lock to the method, the code is as follows:


  public synchronized static LogUtil getInstance() { 
    if (logUtilInstance == null) { 
      logUtilInstance = new LogUtil(); 
    } 
   
    return logUtilInstance; 
  } 


In this way, only one thread is allowed to execute the code in getInstance at a time, which effectively solves the situation where two instances are created above.
Your leader looks at your new code and says, "well, that's good. This does address the possibility of creating two instances, but there is still a problem with the code."

You're nervous. Why would you have a problem?

Your leader smiles: "don't be nervous, this is not a bug, but the performance can be optimized. You see, if I had added a synchronized to the getInstance method, I would have been affected by the synchronized lock every time I tried to execute the getInstace method, which would have made it less efficient to run it. Let me show you how to optimize it better."

First remove the synchronized keyword from the method declaration and add it to the method body:


  public static LogUtil getInstance() { 
    if (logUtilInstance == null) { 
      synchronized (LogUtil.class) { 
        if (logUtilInstance == null) { 
          //This is necessary because it is possible for both processes to execute before synchronized
          logUtilInstance = new LogUtil(); 
        } 
      } 
    } 
   
    return logUtilInstance; 
  } 


After the code changes to this, it will only enter line 3 if the sLogUtil has not been initialized, and then add the synchronization lock. Once the sLogUtil is initialized, the third line is never reached, so the execution of the getInstance method will no longer be affected by the synchronous lock, which will improve the efficiency.
You can't help but say how clever it is, how clever it is to think of it.

Your leader immediately becomes modest: "this method is called double-check Locking, which is not my idea. For more information, you can look it up on the Internet."

In fact, I'm more used to using hunk mode to implement singletons in Java

Lazy is characterized by lazy loading, where instances are not loaded until they are used

The feature of the hungry Chinese style is that it is loaded at the beginning, so it can be returned directly every time it is used (I recommend this one, because it does not need to consider too much multi-threaded security issues, of course, lazy Chinese style can solve the synchronization problem through the double locking mentioned above).

The following code is used to realize the logging in hung-han style:


  public class LogUtil { 
    private static final LogUtil logUtilInstance = new LogUtil(); 
   
    public final int DEBUG = 0; 
   
    public final int INFO = 1; 
   
    public final int ERROR = 2; 
   
    public final int NOTHING = 3; 
   
    public int level = DEBUG; 
   
    private LogUtil() { 
   
    } 
   
    public static LogUtil getInstance() { 
      return logUtilInstance; 
    } 
   
    public void debug(String msg) { 
      if (DEBUG >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public void info(String msg) { 
      if (INFO >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public void error(String msg) { 
      if (ERROR >= level) { 
        System.out.println(msg); 
      } 
    } 
   
    public static void main(String[] args) { 
      logUtil.getInstance().debug("Hello World!"); 
    } 
  } 



Related articles: