Hungry and lazy style of singleton mode

There are two common singleton patterns: hungry and lazy.

Hungry Chinese code is as follows:

/**
 * 单例模式之饿汉式
 * @author leon
 * @time 2018年4月27日 下午2:28:14
 */
public class HungrySingleton {
    // 三要素
    // 1.私有对象
    private static  HungrySingleton hungrySingleton = new HungrySingleton();
    // 2.私有构造器
    private HungrySingleton() {}
    // 3.返回实例的静态方法
    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }
}

Hungry-style instance objects are generated when the class is initialized, and class initialization is thread-safe, so Hungry-style is also thread-safe. However, if its object is particularly large, similar to MyBatis's Configuration, the initialization speed of the application will be relatively slow, that is, there is no lazy loading feature.

Lazy code is as follows:

/**
 * 2. 懒汉式单例模式
 * @author leon
 * @time 2018年4月12日 上午6:54:45
 */
public class LazySingleton{
    // 三要素
    // 1.私有对象
    private static final LazySingleton LAZY_SINGLETON;
    // 2.私有构造器
    private LazySingleton() throws Exception {}
    // 3.返回实例的静态方法
    public static synchronized LazySingleton getInstance() throws Exception {
        if(LAZY_SINGLETON == null) {
            LAZY_SINGLETON = new LazySingleton();
        }
        return LAZY_SINGLETON;
    }
}

The lazy-type instance object is not generated when the class is initialized, but is generated in the synchronous static method that returns the instance, which realizes the feature of lazy loading. Due to the problem of java bytecode rearrangement and lock holding and release, it may cause the problem of repeated object generation, so it is not thread-safe.

Reflection and serialization will generate different objects. Let's analyze how to avoid these two situations.

The first is reflection. There are three ways to generate class objects by reflection: Class.forName("");obj.class;Class.class. Through reflection, we can obtain the class object, and then obtain the constructor, and then generate the instance object through the constructor by converting the properties of the constructor to public. code show as below:

LazySingleton lazySingleton1 = LazySingleton.getInstance();
LazySingleton lazySingleton2 = LazySingleton.getInstance();
// 并没有重写hashCode和equals方法,因此下面比较的是对象引用的内存地址
System.out.println(lazySingleton1 == lazySingleton2);// true    
Constructor<LazySingleton> constructor = LazySingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
LazySingleton lazySingleton3 = constructor.newInstance();
System.out.println(lazySingleton2 == lazySingleton3);// false

Faced with this problem, there is no good solution yet. The solution is to throw an exception that repeatedly generates objects in the private constructor. The code is as follows:

private LazySingleton() throws Exception {
    // 重复生成对象的时候,抛出异常,防止通过反射生成
    if(lazySingleton != null) {
        throw new Exception("LazySingleton 是单例对象,不能重复生成");
    }
}

The second is serialization. First of all, LazySingleton must implement the serialization interface Serializable. Through the following code test, we can see that deserialization generates different instance objects. The test code is as follows:

testObjectOutputStream(lazySingleton2, new File("e:\\obj.txt"));
LazySingleton lazySingleton4  = testObjectInputStream(new File("e:\\obj.txt"));
System.out.println(lazySingleton2 == lazySingleton4);//false

// 反序列化
@SuppressWarnings("unchecked")
public static <T> T testObjectInputStream(File file) {// 读
    try(InputStream in = new FileInputStream(file);ObjectInputStream objectInputStream = new ObjectInputStream(in);){
        return (T) objectInputStream.readObject();
    }catch(Exception e){
        return null;
    }
}
// 序列化
public static boolean testObjectOutputStream(Object obj, File file) {// 写
    try(OutputStream out = new FileOutputStream(file);ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);){
        objectOutputStream.writeObject(obj);
    }catch(Exception e){
        return false;
    }
    return true;
}

How to solve the problem of deserialization is to add the following method inside the class:

// 防止通过反序列化的方式生成重复对象
private Object readResolve() throws ObjectStreamException {
    return lazySingleton;
}

The test result does generate the same object, the test code is the same as before and the result is as follows:

System.out.println(lazySingleton2 == lazySingleton4);//true

Why was this problem solved by adding this code? Looking at the source code, you can see that when the readResolve method is implemented, the instance object returned by this method is called by reflection during serialization (see ObjectInputStream and ObjectStreamClass for details).

There are three other types: double-checked locking; enumeration; static inner class, discussed in a later blog post.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325683331&siteId=291194637