A lazy introduction to the singleton pattern

The most basic spelling of lazy man

The code shown at the end contains detailed processes and annotations, which can be understood in conjunction with the final code.

spelling

Let's take a look at the most basic lazy-man-style writing first, without considering any security issues

package singleton;

public class LazyMan {

    private LazyMan() {
        System.out.println(Thread.currentThread().getName()+"ok");
    }
    
    private static LazyMan lazyMan = null;
    
    public static LazyMan getInstance() {
        if(lazyMan==null) {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }

}
复制代码

test

Then we write a test code to see if it satisfies the requirements of the singleton

public static void main(String[] args) {
    for (int i=0;i<10;i++) {
        new Thread(()->{
            LazyMan.getInstance();
        }).start();
    }
}
复制代码

result

Let's take a look at the output

image.png

From the picture, we can clearly see that this does not follow the requirements of the singleton pattern, so we need to solve the thread safety problem of the singleton.

Double detection mode to solve lazy thread safety problem

code

Here is the improved code:

package singleton;

public class LazyMan {

    private LazyMan() {
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private static LazyMan lazyMan = null;

    public static LazyMan getInstance() {
        if(lazyMan==null) {
            synchronized (LazyMan.class) {
                if(lazyMan==null) {
                    lazyMan = new LazyMan();
                }
            }
        }

        return lazyMan;
    }

}
复制代码

test

Let's test it

public static void main(String[] args) {
    for(int i=0;i<10;i++) {
        new Thread(()->{
            LazyMan.getInstance();
        }).start();
    }
}
复制代码

result

image.png

At this point, we solved the thread safety problem, so it seems that the problem is basically solved. But when we were interviewing, most people could write this way of writing, so we needed some tricks.

Some Lazy Man's Shaking Operations

Breaking the double-check lock pattern

First, let's break the double detection lock mode above, and the following is a demo of the broken code

public static void main(String[] args) throws Exception {
    LazyMan test1 = LazyMan.getInstance();
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    LazyMan test2 = declaredConstructor.newInstance();
    System.out.println(test1);
    System.out.println(test2);

}
复制代码

Let's take a look at the results

image.png

We see that the result at this time does not conform to the singleton pattern.

Resolve operations that break double-detection locks

We only need one step to solve this problem, which is to manually throw exceptions. The implementation is as follows:

private LazyMan() {
    if(lazyMan!=null) {
        throw new RuntimeException("不可以通过反射来破坏哦~");
    }
}
复制代码

Let's take a look at the results at this point:

image.png

We can see that the problem is solved by a single reflection at this time. But why write a reflection here? Without a friend asking, if we both obtained instances through reflection, will something happen again? The answer is yes. Below we continue to dig deeper into this issue.

Continue to break operations that have resolved breaking double-checked locks

code directly

public static void main(String[] args) throws Exception {
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    LazyMan test1 = declaredConstructor.newInstance();
    LazyMan test2 = declaredConstructor.newInstance();
    System.out.println(test1);
    System.out.println(test2);

}
复制代码

Let's take a look at the results

image.png

Ah, it was really destroyed (^-^)V. But the devil is one foot high and the Tao is one foot high, so there must be a solution. Let's look at the solution below.

Solve the operation of obtaining an instance through two reflections

Without further ado, let's look directly at the code

private static boolean del = false;

private LazyMan() {
    synchronized (LazyMan.class){
        if(del==false) {
            del = true;
        } else  {
            throw new RuntimeException("不可以通过反射来破坏哦~");
        }
    }

}
复制代码

Let's take a look at the results

image.png

We solved this problem by judging by creating the del flag. But we know that reflection is omnipotent, so this workaround can still be broken.

destroy the flag

Let's look directly at the code

public static void main(String[] args) throws Exception {
    Field del = LazyMan.class.getDeclaredField("del");
    del.setAccessible(true);
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    LazyMan test1 = declaredConstructor.newInstance();
    del.set(test1,false);
    LazyMan test2 = declaredConstructor.newInstance();
    System.out.println(test1);
    System.out.println(test2);

}
复制代码

Let's take a look at the results

image.png

We can see that the singleton pattern is broken again. Help, the problem has not been solved until now, isn't this a nesting doll, let's talk about how to solve this kind of problem.

final solution

We know that enumeration cannot be obtained by reflection, so we can only prevent the destruction of reflection by enumerating classes. Guys, have you thought about it? Well, this is my lazy introduction to the singleton pattern, and you are welcome to give pointers.

The final code is as follows (with detailed annotations)

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

//懒汉式单例
//道高一尺,魔高一丈,还是没能解决反射造成的安全问题,想解决得看枚举
public class LazyMan {

   //这个的目的是解决俩次都是调用反射创建对象不安全的问题
   private static boolean delTwoFanShe = false;

   private LazyMan(){

//        System.out.println(Thread.currentThread().getName()+"ok");

       synchronized (LazyMan.class) {

           if(delTwoFanShe == false) {
               delTwoFanShe = true;
           } else {
               throw new RuntimeException("不要试图使用反射破坏异常");
           }

//            if(lazyMan!=null) {
//                //此方式是解决创建实例时一个用的getInstance(),另一个用的反射得到的,但是解决不了俩次都是通过反射获得的情况
//                throw new RuntimeException("不要试图使用反射破坏异常");
//            }
       }

   }

   private volatile static LazyMan lazyMan = null;

   //双重检测锁模式的懒汉式模式,简称DCL懒汉式
   public static LazyMan getInstance() {
       //上锁
       if(lazyMan==null) {
           synchronized (LazyMan.class) {
               if(lazyMan==null) {
                   lazyMan = new LazyMan();//不是一个原子性操作
                   /**
                    * 1.分配内存空间
                    * 2.执行构造方法,初始化对象
                    * 3.把这个对象指向这个空间
                    * 假如不是按照123步骤执行的话,就是发生了指令重排,那么lazyMan就没有完成初始化操作
                    */
               }
           }
       }

       return lazyMan; //如果不是按顺序执行123操作的话,此时lazyMan还没有完成构造,所以必须加上volatile来避免这种问题的发生
   }


   //我们写main函数来破坏,不断增强懒汉式的安全性
   public static void main(String[] args) throws Exception {

//        for(int i=0;i<10;i++) {
//            new Thread(()->{
//                LazyMan.getInstance();
//            }).start();
//        }

//        LazyMan instance = LazyMan.getInstance();  //这是一般的获取方式

       //我们得到自己设置的隐藏值,进行破坏
       Field delTwoFanShe = LazyMan.class.getDeclaredField("delTwoFanShe");
       delTwoFanShe.setAccessible(true);

       Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);

       declaredConstructor.setAccessible(true);

       LazyMan lazyMan = declaredConstructor.newInstance();//通过反射得到实例对象

       //我们修改第一次的实例,来将值变成false,进行破坏
       delTwoFanShe.set(lazyMan,false);

       LazyMan lazyMan1 = declaredConstructor.newInstance();//俩次实例对象都由反射来实现,造成破坏

       System.out.println(lazyMan);
       System.out.println(lazyMan1);

   }

}
复制代码

Guess you like

Origin juejin.im/post/7086738661227626503