Multithreading-Singleton Mode Safety Issues

Author: Lonely Smoke, from: http://rjzheng.cnblogs.com/

Hungry Chinese

In fact, everyone knows this, not much to say, on the code.

package singleton;
public class Singleton1 {
    private static Singleton1 instance = new Singleton1();
    private Singleton1 (){}
    public static Singleton1 getInstance() {
        return instance;
    }
}

The advantage is thread safety, the disadvantage is obvious, the object is instantiated when the class is loaded, which wastes space. Therefore, the lazy singleton pattern was proposed.

lazy

1 Lazy Man v1

package singleton;
public class LazySingleton1 {
    private static LazySingleton1 instance;
    private LazySingleton1 (){}
    public static LazySingleton1 getInstance() {
        if (instance == null) {
            instance = new LazySingleton1();
        }
        return instance;
    }

However, this version of the thread is not safe, so for thread safety, the synchronized modifier is added to the getInstance() method, so the getInstance() method is as follows

public static synchronized LazySingleton1 getInstance() {
        if (instance == null) {
            instance = new LazySingleton1();
        }
        return instance;
    }

However, adding synchronized to the method greatly reduces the performance (syncrhonized will cause thread blocking), so a singleton design pattern with double check lock is proposed, which not only ensures thread safety, but also improves performance. The getInstance() method of the double check lock is as follows

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

2 Lazy-style v2 The last double-check lock version of Lazy-style v1, no matter how superior the performance is, it still uses the synchronized modifier. Since this modifier is used, it will have some impact on performance, so the lazy man The v2 version of the formula was born. But before we talk about this version, let's review the loading mechanism of inner classes. The code is as follows

package test;
public class OuterTest {
    static {
        System.out.println("load outer class...");
    }
    // 静态内部类
    static class StaticInnerTest {
        static {
            System.out.println("load static inner class...");
        }
        static void staticInnerMethod() {
            System.out.println("static inner method...");
        }
    }
    public static void main(String[] args) {
        OuterTest outerTest = new OuterTest(); // 此刻其内部类是否也会被加载?
        System.out.println("===========分割线===========");
        OuterTest.StaticInnerTest.staticInnerMethod(); // 调用内部类的静态方法
    }
}

The output is as follows

load outer class...
===========分割线===========
load static inner class...
static inner method

Therefore, we have the following conclusions

  • When a class is loaded, its inner classes are not loaded at the same time.
  • A class is loaded if and only if one of its static members (static fields, constructors, static methods, etc.) is called. .

Based on the above conclusions, we have the lazy man V2 version, the code is as follows

package singleton;
public class LazySingleton2 {
    private LazySingleton2() {
    }
    static class SingletonHolder {
        private static final LazySingleton2 instance = new LazySingleton2();
    }
    public static LazySingleton2 getInstance() {
        return SingletonHolder.instance;
    }
}

Because the object instantiation is constructed when the inner class is loaded, this version is thread-safe (because the object is created in the method, there is a concurrency problem, the static inner class is loaded with the method call, only loaded once, not There are concurrency issues, so it's thread-safe).

In addition, the synchronized keyword is not used in the getInstance() method, so there is no extra performance penalty. When the LazySingleton2 class is loaded, its static inner class SingletonHolder is not loaded, so the instance object is not constructed.

When we call the LazySingleton2.getInstance() method, the internal class SingletonHolder is loaded, and the singleton object is constructed at this time. Therefore, this way of writing saves space and achieves the purpose of lazy loading. This version is also the recommended version in many blogs.

ps: In fact, the enumeration singleton pattern has similar performance, but for readability reasons, it is not the most recommended version.

3 Lazy-style v3 However, the lazy-style v2 version will destroy the singleton structure under the action of reflection. The test code is as follows

package test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import singleton.LazySingleton2;
/**
 * @author zhengrongjun
 */
public class LazySingleton2Test {
    public static void main(String[] args) {
        //创建第一个实例
        LazySingleton2 instance1 = LazySingleton2.getInstance();
        //通过反射创建第二个实例
        LazySingleton2 instance2 = null;
        try {
            Class<LazySingleton2> clazz = LazySingleton2.class;
            Constructor<LazySingleton2> cons = clazz.getDeclaredConstructor();
            cons.setAccessible(true);
            instance2 = cons.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //检查两个实例的hash值
        System.out.println("Instance 1 hash:" + instance1.hashCode());
        System.out.println("Instance 2 hash:" + instance2.hashCode());
    }
}

The output is as follows

Instance 1 hash:1694819250
Instance 2 hash:1365202186

According to the hash value, it can be seen that reflection destroys the characteristics of singleton, so the lazy V3 version was born

package singleton;
public class LazySingleton3 {
    private static boolean initialized = false;
    private LazySingleton3() {
        synchronized (LazySingleton3.class) {
            if (initialized == false) {
                initialized = !initialized;
            } else {
                throw new RuntimeException("单例已被破坏");
            }
        }
    }
    static class SingletonHolder {
        private static final LazySingleton3 instance = new LazySingleton3();
    }
    public static LazySingleton3 getInstance() {
        return SingletonHolder.instance;
    }
}

At this point, run the test class again, and the following prompt appears

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 test.LazySingleton3Test.main(LazySingleton3Test.java:21)
Caused by: java.lang.RuntimeException: 单例已被破坏
    at singleton.LazySingleton3.<init>(LazySingleton3.java:12)
    ... 5 more
Instance 1 hash:359023572

Here it is guaranteed that reflection cannot destroy its singleton feature

4 Lazy Man v4 In distributed systems, in some cases you need to implement the Serializable interface in a singleton class. This way you can store its state in the filesystem and retrieve it at a later point in time.

Let's test if this lazy v3 version remains a singleton after serialization and deserialization.

first

public class LazySingleton3

change into

public class LazySingleton3 implements Serializable

The above test class is as follows

package test;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import singleton.LazySingleton3;
public class LazySingleton3Test {
    public static void main(String[] args) {
        try {
            LazySingleton3 instance1 = LazySingleton3.getInstance();
            ObjectOutput out = null;
            out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
            out.writeObject(instance1);
            out.close();
            //deserialize from file to object
            ObjectInput in = new ObjectInputStream(new FileInputStream("filename.ser"));
            LazySingleton3 instance2 = (LazySingleton3) in.readObject();
            in.close();
            System.out.println("instance1 hashCode=" + instance1.hashCode());
            System.out.println("instance2 hashCode=" + instance2.hashCode());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The output is as follows

instance1 hashCode=2051450519
instance2 hashCode=1510067370

Obviously, we see two more instance classes. To avoid this problem, we need to provide an implementation of the readResolve() method. readResolve() replaces reading the object from the stream. This ensures that no one can create new instances during serialization and deserialization.

Therefore, we provide the code of the lazy V4 version as follows

package singleton;
import java.io.Serializable;
public class LazySingleton4 implements Serializable {
    private static boolean initialized = false;
    private LazySingleton4() {
        synchronized (LazySingleton4.class) {
            if (initialized == false) {
                initialized = !initialized;
            } else {
                throw new RuntimeException("单例已被破坏");
            }
        }
    }
    static class SingletonHolder {
        private static final LazySingleton4 instance = new LazySingleton4();
    }
    public static LazySingleton4 getInstance() {
        return SingletonHolder.instance;
    }
    private Object readResolve() {
        return getInstance();
    }
}

At this point, when running the test class, the output is as follows

instance1 hashCode=2051450519
instance2 hashCode=2051450519

This means that the serialized and deserialized objects are guaranteed to be consistent at this time

Summarize

This article presents several versions of the singleton pattern for us to use in our projects. In fact, in actual projects, we generally choose one of the three from lazy v2, lazy v3, and lazy v4 according to the actual situation. It is not necessary to choose lazy v4 as a singleton to implement. In the end, I hope you all have something to gain.

Guess you like

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