Offer to prove safety fluent Series - implements the Singleton pattern

2 face questions: Singleton pattern achieved

Title: Design a class, we can only generate an instance of the class.

Due to the design pattern plays an important role in object-oriented programming, in the interview process, many companies like to ask some questions related to the design mode. In the conventional mode, Singleton mode can be used only a span of a few lines of code to achieve complete. Therefore, writing a Singleton is a very common type of interview questions.

Here Insert Picture Description

Written before, if you read my column design patterns, then you'll be thinking of this question open.

Important single embodiment has three modes: first 某个类只能有一个实例; second 它必须自行创建这个实例; third 它必须自行向整个系统提供这个实例.

Here we look at its implementation

First, the lazy man's writing

public class LazySingleton {

    private static LazySingleton lazySingleton = null;

    private LazySingleton() {

    }

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

The key is to constructor private, restrictions can only be obtained through an internal instance of a static method.

But such an approach, it is clear that is not thread safe. If multiple threads before the class initialization, there is more than one thread calls getinstance method and lazySingleton == null determine when conditions are right, this time will lead to a new LazySingleton multiple instances. So to change it:

The wording is called DoubleCheck. Entered if (lazySingleton == null) for a case where a plurality of code blocks before the class initialization threads

This time lock control, again determines if (lazySingleton == null), if the conditions are met out of the new one instance, turn to the other thread determines when naturally it is false, the problem is almost solved.

public class LazyDoubleCheckSingleton {

    private static LazyDoubleCheckSingleton lazySingleton = null;

    private LazyDoubleCheckSingleton() {

    }

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

But even so, improve the code above still some problems can not be solved.

Because there will be reordering problem. Reordering is a compiler optimization techniques, are content "compiler theory" is, do not discuss in detail here, but to tell you how it was.

Normally, the following code

lazySingleton = new LazyDoubleCheckSingleton();

When the implementation is this:
1. Allocate memory for the object
2. initialize the object
3. Set LazyDoubleCheckSingleton point just allocated memory address.

But the compiler optimization, this may be the way
1. to allocate memory for the object
3. Set LazyDoubleCheckSingleton point just allocated memory address.
2. Initialize the object

Step 2 and Step 3 and negative, on the problem. (Provided, the compiler compiler optimization)
for example there are two threads, thread names are 1 and Thread 2. Thread 1 entered if (lazySingleton == null) block, get locked, were new LazyDoubleCheckSingleton()performed, when loading instances of the class structure, provided LazyDoubleCheckSingleton point just assigned memory address, but the object is not initialized. Thread 2 judgment if (lazySingleton == null) is false, return directly lazySingleton, they were using, when used will be a problem.

Painted two pictures of it:

Where reordered as follows:

Here Insert Picture Description
Look at where the problem

Here Insert Picture Description
Of course, this very good improvement, start in terms of disabled reordering, adding a volatile. Not familiar with the security thread can refer to this article [Java Concurrency] Detailed thread safety

    private volatile static LazyDoubleCheckSingleton lazySingleton = null;

Well more than one way, you can also use object initialization of "visibility" to solve, specifically the use of static inner classes based on class initialization lazy loading, the name is very long, but it is not difficult to understand. (Using this method, do not worry about problems caused by the above compiler optimization)

JVM class initialization lazy loading and closely related, we demonstrate examples of just being loaded only, and not linked and initialized.

We look at the implementation:
definition of a static inner classes, static field instance of a singleton. Gets the singleton need to call the getInstance indirect acquisition.

public class StaticInnerClassSingleton {

    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }

    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }
}

If you are not familiar with the inner class, you can refer to this article [volume] Java core technology in-depth understanding of Java's inner classes

Here Insert Picture Description
Lazy formula presentation right here, to achieve the following look another embodiment single mode


Second, hungry Chinese-style writing

Show you the basic wording

public class HungrySingleton {

    // 类加载的时候初始化
    private final static HungrySingleton hungrySingleton = new HungrySingleton();

    /*
    也可以在静态块里进行初始化
      private static HungrySingleton hungrySingleton;

     static {
        hungrySingleton = new HungrySingleton();
     }
     */
    private HungrySingleton() {

    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

}

饿汉式在类加载的时候就完成单例的实例化,如果用不到这个类会造成内存资源的浪费,因为单例实例引用不可变,所以是线程安全的

同样,上面的饿汉式写法也是存在问题的

我们依次看一下:

首先是序列化破坏单例模式

先保证饿汉式能够序列化,需要继承Serializable 接口。

import java.io.Serializable;

public class HungrySingleton implements Serializable {

    // 类加载的时候初始化
    private final static HungrySingleton hungrySingleton = new HungrySingleton();

    /*
    也可以在静态块里进行初始化
      private static HungrySingleton hungrySingleton;

     static {
        hungrySingleton = new HungrySingleton();
     }
     */
    private HungrySingleton() {

    }

    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

}

我们测试一下:

import lombok.extern.slf4j.Slf4j;

import java.io.*;

@Slf4j
public class Test {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
       HungrySingleton hungrySingleton = HungrySingleton.getInstance();
       ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton"));
       oos.writeObject(hungrySingleton);

       File file = new File("singleton");
       ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));

        HungrySingleton newHungrySingleton = (HungrySingleton) ois.readObject();

        log.info("结果 {}",hungrySingleton);
        log.info("结果 {}",newHungrySingleton);
        log.info("对比结果 {}",hungrySingleton == newHungrySingleton);
    }
}

结果:
Here Insert Picture Description
结果发现对象不一样,原因就涉及到序列化的底层原因了,我们先看解决方式:

饿汉式代码中添加下面这段代码

private Object readResolve() {
        return hungrySingleton;
    }

重新运行,这个时候的结果:

Here Insert Picture Description
原因出在readResolve方法上,下面去ObjectInputStream源码部分找找原因。(里面都涉及到底层实现,不要指望看懂)

在一个读取底层数据的方法上有一段描述
就是序列化的Object类中可能定义有一个readResolve方法。我们在二进制数据读取的方法中看到了是否判断

Here Insert Picture Description
private Object readOrdinaryObject()方法中有这段代码,如果存在ReadResolve方法,就去调用。不存在,不调用。联想到我们在饿汉式添加的代码,大致能猜到怎么回事了吧。

Here Insert Picture Description
***

另外一种情况就是反射攻击破坏单例

演示一下

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

@Slf4j
public class Test {

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class objectClass = HungrySingleton.class;

        Constructor constructor = objectClass.getDeclaredConstructor();
        constructor.setAccessible(true); // 强行打开构造器权限
        HungrySingleton instance = HungrySingleton.getInstance();
        HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();

        log.info("结果{}",instance);
        log.info("结果{}",newInstance);
        log.info("比较结果{}",newInstance == instance);
    }
}

Here Insert Picture Description
这里强行破开了private的构造方法的权限,使得能new出来一个单例实例,这不是我们想看到的。

解决方法是在构造方法中抛出异常

   private HungrySingleton() {
        if( hungrySingleton != null) {
            throw new RuntimeException("单例构造器禁止反射调用");
        }
    }

这个时候再运行一下

Here Insert Picture Description
其实对于懒汉式也是有反射破坏单例的问题的,也可以采用类似抛出异常的方法来解决。

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

  • 饿汉式单例类在自己被加载时就将自己实例化。单从资源利用效率角度来讲,这个比懒汉式单例类稍差些。从速度和反应时间角度来讲,则比懒汉式单例类稍好些。
  • Example lazy single class instantiation, have good handling multiple threads to access a first reference such limitations, especially when the resource initialization singleton class as the resource controller when the resource initialization necessarily involves instantiation, and is likely to spend a lot of time, which means there is more threads simultaneously for the first time the chances of such a reference becomes larger, need to be controlled by the synchronization mechanism.

Third, the enumeration

In addition to implement a singleton pattern is an enumeration

Using enumerations way to achieve Singleton pattern is "Effective Java" way of pushing, in a lot of good open source code can often see the enum way to achieve Singleton place, enumerated types are not allowed to inherit, the same is thread-safe and can only be instantiated once, but enumerated types can not be lazy loading of Singleton actively used, such as static method in which a call is immediately iNSTANCE get instantiated.

//枚举类型本身是final的,不允许被继承
public enum Singleton
{
    INSTANCE;
    //实例变量
    private byte[] data = new byte[1024];

    Singleton()
    {
        System.out.println("I want to follow Jeffery.");
    }

    public static void method()
    {
        //调用该方法则会主动使用Singleton,INSTANCE将会被实例化
    }

    public static Singleton getInstance()
    {
        return INSTANCE;
    }
}

In the actual interview, we have to show the enumeration singleton pattern, can be written like this:

public enum Singleton
{
    INSTANCE;
  
    public static Singleton getInstance()
    {
        return INSTANCE;
    }
}

Java enumeration is a kind of syntactic sugar, in other words, the compiler help us do some of the things we will decompile bytecode into Java code, the compiler to help us look at what has been done, and explore why using enumerations way to achieve singleton pattern is "Effective Java" way of pushing?

Original code is as follows:

public enum EnumClass {
    SPRING,SUMMER,FALL,WINTER;
}

After the anti-compiled code

public final class EnumClass extends Enum
{

    public static EnumClass[] values()
    {
        return (EnumClass[])$VALUES.clone();
    }

    public static EnumClass valueOf(String name)
    {
        return (EnumClass)Enum.valueOf(suger/EnumClass, name);
    }

    private EnumClass(String s, int i)
    {
        super(s, i);
    }

    public static final EnumClass SPRING;
    public static final EnumClass SUMMER;
    public static final EnumClass FALL;
    public static final EnumClass WINTER;
    private static final EnumClass $VALUES[];

    static 
    {
        SPRING = new EnumClass("SPRING", 0);
        SUMMER = new EnumClass("SUMMER", 1);
        FALL = new EnumClass("FALL", 2);
        WINTER = new EnumClass("WINTER", 3);
        $VALUES = (new EnumClass[] {
            SPRING, SUMMER, FALL, WINTER
        });
    }
}

For static reference block do not understand: the Java static code block, code block structure, constructors, Common block

Combined with the foregoing, it is not very easy to understand? In addition, we can also see that the enumeration Enum class is inherited, but it is also the final, that is, not inherited.

Play enumeration type of Singleton There are many more of the following categories of online pass:

Internal enumeration class form

Example 1. The method of construction of the object (note yet been mentioned above)

public class EnumSingleton {
    private EnumSingleton(){}
    
    public static EnumSingleton getInstance(){
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton{
        INSTANCE;
        
        private EnumSingleton singleton;
        
        //JVM会保证此方法绝对只调用一次
        Singleton(){
            singleton = new EnumSingleton();
        }
        public EnumSingleton getInstance(){
            return singleton;
        }
    }
}

2. enumeration constant value is the object instance

public class EnumSingleton {
    private EnumSingleton(){}
    
    public static EnumSingleton getInstance(){
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton{
        INSTANCE(new EnumSingleton());
        private EnumSingleton singleton;
        
        //JVM会保证此方法绝对只调用一次
        Singleton(EnumSingleton singleton){
            this.singleton = singleton;
        }
        public EnumSingleton getInstance(){
            return singleton;
        }
    }
}

Interface in the form of
enum embodiment mode for a single standard, or to achieve the best writing interface in the form:

// 定义单例模式中需要完成的代码逻辑
public interface MySingleton {
    void doSomething();
}

public enum Singleton implements MySingleton {
    INSTANCE {
        @Override
        public void doSomething() {
            System.out.println("I want to follow Jeffery. What about you ?");
        }
    };

    public static MySingleton getInstance() {
        return Singleton.INSTANCE;
    }
}

I asked! Interview singletons, not afraid of you afraid of?

Guess you like

Origin www.cnblogs.com/JefferyChenXiao/p/12244387.html