Java design patterns study notes (five) Singleton

Foreword

Benpian is a design pattern study notes One of the articles, such as interested in other modes, you can find from that address design pattern study notes summary address

1. The reason of using the singleton

To Windows Task Manager, for example, in Windows, Task Manager is unique, many times to open the Task Manager, always only a single pop-up task manager.

There are two reasons to do so:

  1. save resources
  2. Avoid multiple instances of data inconsistency

1.1 conserve resources

If you can pop up multiple windows, and the contents of these windows are exactly the same, all duplicate objects, which is bound to waste system resources , task management needs to get a lot of information about the system is running, access to such information needs to consume some system resources, resources include CPU and memory resources, waste is shameful, and there is no need exactly the same content multiple window display

1.2 Avoid multiple instances of data inconsistency

If multiple pop-up windows inconsistent, the problem is more serious, which means there are multiple states at a certain instant information system resource usage and processes, and other services, such as Task Manager window displays A "CPU usage" as 10%, window B displays "CPU usage rate" of 15%, in the end what is true? This is purely a "molested" users, giving users a misunderstanding, more desirable.

In order to ensure uniqueness of the objects motives, we can be done by Singleton pattern, this is where the Singleton pattern

2. Overview of singleton

By simulating the Windows Task Manager, create TaskManager class to understand the singleton pattern

2.1 TaskManager

      /**
       * @author liuboren
       * @Title: 任务管理器类
       * @Description:
       * @date 2019/7/16 15:17
       */
      public class TaskManager {
          //初始化窗口
          public TaskManager() {
      
          }
      
          //显示进程
          public void displayProcesses() {
      
          }
      
          //显示服务
          public void displayServices() {
      
          }
      
      }
      

2.2 TaskManager reconstruction

In order to realize the uniqueness of the Windows Task Manager, we have to reconstruct the class through the following three steps:

  • Constructor private modification
  • Define a private member variable of type TaskManager
  • Increasing the total static method instantiates TaskManager

2.2.1 constructor private modification

Because each will have when using the new keyword to instantiate a new object TaskManager class, in order to ensure uniqueness TaskManager instance, we need to ban the use of new external class directly to create the object, so you need to be seen TaskManager constructor instead of private, as shown in the following code:

          private TaskManager() {……}

2.2.2 TaskManager definition of a type of private member variables

How will the constructor be changed after private modifications that create objects? Do not worry, though to create an object outside the class is no longer available new, but still within the TaskManager can create, visibility is only valid for outside class. Therefore, we can create and save the only instance in TaskManager. In order to let the public have access to this unique instance, you need to define a static TaskManager TaskManager in the type of private member variables, the code shown below:

          private static TaskManager tm = null;

2.2.3 increase the total static method instantiates TaskManager

In order to ensure encapsulation member variables, we will set the visibility tm TaskManager object type is private, but the outside world that how to use the member variables and when to instantiate the member variables? The answer is to add a public static method,
as shown in the following code:

          public static TaskManager getInstance()
          {
          if (tm == null)
          {
          tm = new TaskManager();
          }
          return tm;
          }

2.2.4 Code

Outside the class we can not directly create new TaskManager object, but the object instance can be accessed through code TaskManager.getInstance (), first call getInstance () will create a unique instance when the method returns the first created when calling again example, in order to ensure uniqueness instance of an object

          
          /**
           * @author liuboren
           * @Title: 单例版任务管理器类
           * @Description:
           * @date 2019/7/16 15:24
           */
          public class TaskManagerSingleton {
              private static TaskManagerSingleton taskManagerSingleton = null;
              
              //初始化窗口
              private TaskManagerSingleton() {
              }
          
              public static TaskManagerSingleton getInstance(){
                  if (taskManagerSingleton == null){
                      taskManagerSingleton = new TaskManagerSingleton();
                  }
                  return taskManagerSingleton;
              }
          
              //显示进程
              public void displayProcesses() {
          
              }
          
              //显示服务
              public void displayServices() {
          
              }
          }
          

2.3 Definitions

Singleton (Singleton Pattern): to ensure that only one instance of a particular class, and to instantiate the instance of the entire system and to provide this class is called singleton class that provides global access method.

Singleton pattern is an object to create schema.

2.4 three points

1. Only one instance of a class.
2. it must create an instance of itself.
3. it must provide their own examples to the entire system.

2.5 Structure FIG.

2.6 Role

Singleton (singleton): implemented in a single embodiment only one class internally generated instance, and it provides a static getInstance () factory method, so that customers can access it only examples; in order to prevent them externally instantiation, which designed for the private constructor; single embodiment the static objects within a class type of a Singleton, the only example of a shared externally.

3. starving formula singleton and lazy formula singleton

   public static TaskManagerSingleton getInstance(){
          if (taskManagerSingleton == null){
              taskManagerSingleton = new TaskManagerSingleton();
          }
          return taskManagerSingleton;
      }

In a highly concurrent environment, the above code is not thread safe and may at the same time, there are two threads by determining if (taskManagerSingleton == null), resulting in two instantiated objects.

To solve this problem, there are two ways one is the lazy man's singleton class. Another example is a hungry man single class .

Note: If you have not had knowledge of concurrency, you can see my previous blog concurrent programming study notes series

3.1 starving formula singleton

      /**
       * @author liuboren
       * @Title: 饿汉式单例
       * @Description:
       * @date 2019/7/16 16:30
       */
      public class EagerSingleton {
          private static final EagerSingleton instance = new EagerSingleton();
          private EagerSingleton() { }
          public static EagerSingleton getInstance() {
              return instance;
          }
      }

When a class is loaded, static instance variables are initialized, then the private class constructor is called, the only instance of singleton class will be created. Example starving single class ensures the uniqueness of the singleton object.

3.2 lazy formula singleton

      /**
       * @author liuboren
       * @Title: 懒汉式单例类
       * @Description:
       * @date 2019/7/16 16:41
       */
      public class LazySingleton {
          private static LazySingleton instance = null;
      
          private LazySingleton() { }
          
          synchronized public static LazySingleton getInstance() {
              if (instance == null) {
                  instance = new LazySingleton();
              }
              return instance;
          }
      }
      

3.2.1 starving singleton performance optimization of formula

The embodiment lazy single class getInstance () method of increasing the front keyword synchronized for thread-locking, to address the problem of simultaneous access to a plurality of threads. You need to be thread-lock determination However, the above code solves the security thread, but each call to getInstance (), multi-threaded high concurrent access environment, it will cause the system performance degradation. How to solve both thread-safety issues do not affect system performance? We continue to improve the lazy single cases. In fact, we do not need the whole getInstance () method to lock, which only need to code "instance = new LazySingleton ();" lock can be. Thus getInstance () method can be modified as follows:

          
          public static LazySingleton getInstance() {
          
          if (instance == null) {
          
          synchronized (LazySingleton.class) {
          
          instance = new LazySingleton();
          
          }
          
          }
          
          return instance;
          
          }
          

3.2.2 double check locks for starving formula singleton

Looks like the problem is resolved, it is not. If the above code to a single embodiment, there is a single still object is not the only embodiment.

原因如下:假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过instance == null的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想,因此需要进行进一步改进,在synchronized中再进行一次(instance == null)判断,这种方式称为双重检查锁定(Double-Check Locking)。使用双重检查锁定实现的懒汉式单例类完整代码如下所示:

          
          /**
           * @author liuboren
           * @Title: 完美版 懒加载单例类
           * @Description:
           * @date 2019/7/16 16:47
           */
          public class LazySingletonPerfect {
              private volatile static LazySingletonPerfect instance = null;
          
              private LazySingletonPerfect() {
              }
          
              public static LazySingletonPerfect getInstance() {
                      //第一重判断
                  if (instance == null) {
                      //锁定代码块
                      synchronized (LazySingleton.class) {
                      //第二重判断
                          if (instance == null) {
                              instance = new LazySingletonPerfect(); //创建单例实例
                          }
                      }
                  }
                  return instance;
              }
          
          }
          

3.3 饿汉式单例类与懒汉式单例类比较

3.3.1 饿汉式

优点:

  1. 线程安全: 饿汉式单例类在类被加载时就将自己实例化,它的优点在于无须考虑多线程访问问题,可以确保实例的唯一性

  2. 调用速度优于懒汉式: 从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例

缺点: 资源利用效率不如懒汉式且加载时间较长无论系统在运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长

3.3.2 懒汉式

优点:

  1. 延迟加载: 懒汉式单例类在第一次使用时创建,无须一直占用系统资源,实现了延迟加载

缺点:

  1. 非线程安全: 多线程访问懒汉式单例类可能会创建多个实例

  2. 性能稍差通过双重检查锁定等机制保证线程安全,这将导致系统性能受到一定影响

4.更好的单例实现方法

饿汉式单例类不能实现延迟加载,不管将来用不用始终占据内存;懒汉式单例类线程安全控制烦琐,而且性能受影响。

可见,无论是饿汉式单例还是懒汉式单例都存在这样那样的问题,有没有一种方法,能够将两种单例的缺点都克服,而将两者的优点合二为一呢?答案是:Yes!下面我们来学习这种更好的被称之为Initialization Demand Holder (IoDH)的技术

  /**
   * @author liuboren
   * @Title: IoDh技术
   * @Description: Java最好的单例实现模式
   * @date 2019/7/17 10:25
   */
  public class IoDHSingleton {
  
      private IoDHSingleton() {
      }
  
      //静态内部类的成员变量才能是静态的
      private static class HolderClas{
  
          public static IoDHSingleton ioDHSingleton = new IoDHSingleton();
      }
  
      public static  IoDHSingleton getInstance(){
          return HolderClas.ioDHSingleton;
      }
  
      public static void main(String[] args) {
          IoDHSingleton s1,s2;
          s1 = IoDHSingleton.getInstance();
          s2 = IoDHSingleton.getInstance();
          System.out.println(s1 == s2);
  
      }
  }
  

编译并运行上述代码,运行结果为:true,即创建的单例对象s1和s2为同一对象。由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。

通过使用IoDH,我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的Java语言单例模式实现方式(其缺点是与编程语言本身的特性相关,很多面向对象语言不支持IoDH)

5. 总结

单例模式作为一种目标明确、结构简单、理解容易的设计模式,在软件开发中使用频率相当高,在很多应用软件和框架中都得以广泛应用

5.1 优点

(1) 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。

(2) 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。

(3) 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。

5.2 缺点

(1) 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。

(2) 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。

(3) 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失

5.3 适用场景

(1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。

(2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

Guess you like

Origin www.cnblogs.com/xisuo/p/11204952.html