Home  >  Article  >  Java  >  Let’s analyze the singleton of java design pattern together

Let’s analyze the singleton of java design pattern together

WBOY
WBOYforward
2022-11-07 16:56:26958browse

This article brings you relevant knowledge about java, which mainly introduces the relevant content about the singleton pattern in the design pattern. The basic principle of a singleton is the class of a singleton object. It will only be initialized once. Let's take a look at it. I hope it will be helpful to everyone.

Let’s analyze the singleton of java design pattern together

Recommended study: "java Video Tutorial"

The single-element enumeration type often becomes the best way to implement Singleton Best method.

What is a singleton? As a basic principle, the class of a singleton object will only be initialized once. In Java, we can say that only one object instance of the class exists in the JVM. In Android, we can say that there is only one object instance of this class during the running of the program.

Simple implementation steps of singleton mode:

  • The construction method is private, ensuring that objects cannot be created from the outside through new.

  • # Provides static methods to obtain instances of this class.

  • #Create an object of this class inside the class and return it through the static method in step 2.

Follow the above steps to write down the singleton pattern that you think is more rigorous, and then see if the singleton you wrote can meet the following conditions:

  • Is your singleton loaded on demand?
  • Is your singleton thread-safe?Involves three elements of concurrency: atomicity, visibility, orderliness
  • Your singleton violent reflection and serialization is it safe?

1. Hungry Chinese Style

//JAVA实现public class SingleTon {    //第三步创建唯一实例
    private static SingleTon instance = new SingleTon();    
    //第一步构造方法私有
    private SingleTon() {
    }    
    //第二步暴露静态方法返回唯一实例
    public static SingleTon getInstance() {        return instance;
    } 
}//Kotlin实现object SingleTon

Advantages: Simple design, solving the problem of multi-threaded instantiation.

Disadvantages: When the virtual machine loads the SingleTon class, the class static variables will be assigned during the initialization phase, that is, when the virtual machine loads the class (The getInstance method may not be called at this time) new SingleTon(); has been called to create an instance of the object. After that, regardless of whether the instance object is used or not, it will occupy memory space.

2. Lazy Man Style

//JAVA实现public class SingleTon {    //创建唯一实例
    private static SingleTon instance = null;    
    private SingleTon() {
    }    
    public static SingleTon getInstance() {        //延迟初始化 在第一次调用 getInstance 的时候创建对象
        if (instance == null) {
            instance = new SingleTon();
        }        return instance;
    } 
}//Kotlin实现class SingleTon private constructor() {    companion object {        private var instance: SingleTon? = null
            get() {                if (field == null) {
                    field = SingleTon()
                }                return field
            }        fun get(): SingleTon{            return instance!!
        }
    }
}

Advantages: The design is also relatively simple, different from Hungry Man Style. When this Singleton is loaded, the static modified by static The variable will be initialized to null. It will not occupy memory at this time. Instead, the instance object will be initialized and created on demand when the getInstance method is called for the first time.

Disadvantages: There is no problem in a single-threaded environment. In a multi-threaded environment, thread safety issues will occur. When two threads run the statement instane == null at the same time and both pass, they will each instantiate an object, so it is no longer a singleton.

How to solve the lazy-style multi-instance problem in a multi-threaded environment?

  • Static inner class

    //JAVA实现public class SingleTon {    
        private static class InnerSingleton{        private static SingleTon singleTon  = new SingleTon();
        }    public SingleTon getInstance(){        return InnerSingleton.singleTon;
        }    
        private SingleTon() {
        }
    }//kotlin实现class SingleTon private constructor() {
        companion object {        val instance = InnerSingleton.instance
        }    private object InnerSingleton {        val instance = SingleTon()
        }
    }
  • Direct synchronization method

    //JAVA实现public class SingleTon {    //创建唯一实例
        private static SingleTon instance = null;    
        private SingleTon() {
        }    
        public static synchronized SingleTon getInstance() {        if (instance == null) {
                instance = new SingleTon();
            }        return instance;
        } 
    }//Kotlin实现class SingleTon private constructor() {  companion object {      private var instance: SingleTon? = null
              get() {              if (field == null) {
                      field = SingleTon()
                  }              return field
              }      @Synchronized
          fun get(): SingleTon{          return instance!!
          }
      }
    }

    Advantages: Only one thread can instantiate the object through locking, which solves the thread safety problem.

    Disadvantages: For static methods, the synchronized keyword will lock the entire Class. Every time the getInstance method is called, the thread will be synchronized, which is very inefficient. Moreover, after the instance object is created, There is no need to continue synchronizing.

    Remarks: The synchronized here ensures the atomicity and memory visibility of the operation.

  • Synchronized code block (double check lock mode DCL)

    //JAVA实现 public class SingleTon {    //创建唯一实例
        private static volatile SingleTon instance = null;    
        private SingleTon() {
        }    
        public static SingleTon getInstance() {        if (instance == null) {
                synchronized (SingleTon.class) {   
                    if (instance == null) {
                        instance = new SingleTon();
                    }
                }
            }        return instance;
        } 
    }//kotlin实现class SingleTon private constructor() {    companion object {        val instance: SingleTon by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
                SingleTon() 
            }
      }
    }
    或者class SingleTon private constructor() {    companion object {        @Volatile private var instance: SingleTon? = null
            fun getInstance() =
                  instance ?: synchronized(this) {
                      instance ?: SingleTon().also { instance = it }
                  }
      }
    }

    Advantages: Added a synchronized code block, Determine whether the instance object exists in the synchronization code block, and create it if it does not exist. This can actually solve the problem, because although multiple threads are used to obtain the instance object, there will only be one at the same time. The thread will enter the synchronized code block, so after the object is created at this time, even if other threads enter the synchronized code block again, since the instance object has been created, they can return directly. But why do we need to judge the instance is empty again in the previous step of the synchronization code block? This is because after we create the instance object, we directly determine whether the instance object is empty. If it is not empty, just return it directly, which avoids entering the synchronization code block again and improves performance.

    Disadvantages: Unable to avoid violent reflection to create objects.

    Remarks: The volatile here plays a role in memory visibility and preventing instruction reordering.

3. Enumeration to implement singleton

public enum SingletonEnum {    INSTANCE;    public static void main(String[] args) {        System.out.println(SingletonEnum.INSTANCE == SingletonEnum.INSTANCE);
    }
}

Enumeration to implement singleton is the most recommended method, because even through serialization , reflection, etc. cannot destroy singletonity. (Regarding the statement that Android’s use of enumeration will cause performance problems, this should be the era of tight memory before Android 2. Due to this so-called performance impact)

四、如何避免单例模式反射攻击

以最初的DCL为测试案例,看看如何进行反射攻击及又如何在一定程度上避免反射攻击。反射攻击代码如下:

 public static void main(String[] args) {

     SingleTon singleton1 = SingleTon.getInstance();
     SingleTon singleton2 = null;

     try {
         Class<SingleTon> clazz = SingleTon.class;
         Constructor<SingleTon> constructor = clazz.getDeclaredConstructor();
         constructor.setAccessible(true);
         singleton2 = constructor.newInstance();
     } catch (Exception e) {
         e.printStackTrace();
     }

     System.out.println("singleton1.hashCode():" + singleton1.hashCode());
     System.out.println("singleton2.hashCode():" + singleton2.hashCode());
 }

执行结果:

 singleton1.hashCode():1296064247
 singleton2.hashCode():1637070917

通过执行结果发现通过反射破坏了单例。 如何保证反射安全呢?只能以暴制暴,当已经存在实例的时候再去调用构造函数直接抛出异常,对构造函数做如下修改:

  public class SingleTon {     //创建唯一实例
     private static volatile SingleTon instance = null;   
     private SingleTon() {         if (instance != null) {             throw new RuntimeException("单例构造器禁止反射调用");
         }
     }   
     public static SingleTon getInstance() {         if (instance == null) {
           synchronized (SingleTon.class) {   
               if (instance == null) {
                   instance = new SingleTon();
               }
           }
       }       return instance;
     } 
 }

此时可防御反射攻击,抛出异常如下:

 java.lang.reflect.InvocationTargetException
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
 at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:45)
 at com.imock.demo.TestUtil.main(TestUtil.java:33)
 Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用
 at com.imock.demo.SingleTon.<init>(SingleTon.java:16)
 ... 6 more Exception in thread "main" java.lang.NullPointerException
 at com.imock.demo.TestUtil.testSingleInstance(TestUtil.java:49)
 at com.imock.demo.TestUtil.main(TestUtil.java:33) 
 Process finished with exit code 1

然后我们把上述测试代码修改如下(调换了singleton1的初始化顺序)

 public static void main(String[] args) {
     SingleTon singleton2 = null;

     try {
         Class<SingleTon> clazz = SingleTon.class;
         Constructor<SingleTon> constructor = clazz.getDeclaredConstructor();
         constructor.setAccessible(true);
         singleton2 = constructor.newInstance();
     } catch (Exception e) {
         e.printStackTrace();
     }

     System.out.println("singleton2.hashCode():" + singleton2.hashCode());

     SingleTon singleton1 = SingleTon.getInstance(); //调换了位置,在反射之后执行
     System.out.println("singleton1.hashCode():" + singleton1.hashCode());
 }

执行结果:

 singleton2.hashCode():1296064247
 singleton1.hashCode():1637070917

发现此防御未起到作用。

缺点:

  • 如果反射攻击发生在正常调用getInstance之前,每次反射攻击都可以获取单例类的一个实例,因为即使私有构造器中使用了静态成员(instance) ,但单例对象并没有在类的初始化阶段被实例化,所以防御代码不生效,从而可以通过构造器的反射调用创建单例类的多个实例;
  • 如果反射攻击发生在正常调用之后,防御代码是可以生效的;

如何避免序列化攻击?只需要修改反序列化的逻辑就可以了,即重写 readResolve() 方法,使其返回统一实例。

   protected Object readResolve() {       return getInstance();
   }

脆弱不堪的单例模式经过重重考验,进化成了完全体,延迟加载,线程安全,反射及序列化安全。简易代码如下:

  • 饿汉模式

    public class SingleTon {    private static SingleTon instance = new SingleTon();    
        private SingleTon() {        if (instance != null) {              throw new RuntimeException("单例构造器禁止反射调用");
             }
        }    public static SingleTon getInstance() {        return instance;
        } 
    }
  • 静态内部类

    public class SingleTon {    
        private static class InnerStaticClass{        private static SingleTon singleTon  = new SingleTon();
        }    public SingleTon getInstance(){        return InnerStaticClass.singleTon;
        }    
        private SingleTon() {       if (InnerStaticClass.singleTon != null) {           throw new RuntimeException("单例构造器禁止反射调用");
           }
        }
    }
  • 懒汉模式

    public class SingleTon {    //创建唯一实例
        private static SingleTon instance = null;    
        private SingleTon() {        if (instance != null) {              throw new RuntimeException("单例构造器禁止反射调用");
            }
        }    
        public static SingleTon getInstance() {        //延迟初始化 在第一次调用 getInstance 的时候创建对象
            if (instance == null) {
                instance = new SingleTon();
            }        return instance;
        } 
    }

    缺点:

    • 如果反射攻击发生在正常调用getInstance之前,每次反射攻击都可以获取单例类的一个实例,因为即使私有构造器中使用了静态成员(instance) ,但单例对象并没有在类的初始化阶段被实例化,所以防御代码不生效,从而可以通过构造器的反射调用创建单例类的多个实例;
    • 如果反射攻击发生在正常调用之后,防御代码是可以生效的。

(枚举实现单例是最为推荐的一种方法,因为就算通过序列化,反射等也没办法破坏单例性,底层实现比如newInstance方法内部判断枚举抛异常)

推荐学习:《java视频教程

The above is the detailed content of Let’s analyze the singleton of java design pattern together. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.im. If there is any infringement, please contact admin@php.cn delete