There are many articles on my blog about the singleton pattern. As the most common of the 23 design patterns, the singleton pattern is not as simple as you might think. There are many issues to consider when designing singletons, such as thread safety and serialization of singletons.

Singletons:

Design pattern ii — singleton pattern

Design pattern 3 — those singletons in the JDK

Seven ways to write a singleton pattern

Singleton and serialization stuff

How do you implement a thread-safe singleton without using synchronized and Lock?

How do you implement a thread-safe singleton without using synchronized and Lock? (2)

If you are not familiar with singletons, or are not aware of the thread-safety issues of singletons and the fact that serialization can break singletons, read the article above. After reading the above six articles, I believe you will have a more in-depth understanding of the singleton pattern.

We know that there are usually seven ways to write the singleton, so what’s the best way to write the singleton? Why is that? This article will take a look.

Which is the best way to write singletons

In Stakcove Flow, there is a question about What is an efficient way to implement a singleton pattern in Java? Discussion:

As shown above, the answer that received the most votes was: Use enumeration.

The respondents cited a point made explicitly by Joshua Bloch in Effective Java:

While the use of enumerations to implement singletons has not been widely adopted, enumerations of single-element types have become the best way to implement singletons.

If you really understand the use of singletons and some of the potential pitfalls, you might come to the same conclusion that using enumerations is a good way to implement singletons.

Enumeration singletons are simple to write

If you look at the code for all the ways to implement a singleton in “Seven Ways to Write a Singleton Pattern,” you’ll see that the code for each way to implement a singleton is complicated. The main reason is to worry about thread safety.

Let’s briefly compare the “double check lock” method and enumeration method to implement the code of the singleton.

“Double check lock” implementation singleton:

public class Singleton { private volatile static Singleton singleton; private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }}Copy the code

Enumeration implements singletons:

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}  
Copy the code

By contrast, you’ll find that enumerations implement singletons in much simpler code.

The code for double-lock verification above is bloated because most of the code is about thread safety. In order to balance thread safety and lock granularity, the code will inevitably be more complex. However, this code is problematic because it does not solve the problem of deserialization breaking singletons.

Enumerations solve thread-safety problems

Mentioned above. Singletons implemented in a non-enumeration manner are thread-safe on their own, so the other methods are necessarily bloated. So why doesn’t using enumerations require addressing thread-safety issues?

It’s not that we don’t need to be thread-safe with enumerations, it’s just that thread-safe guarantees don’t matter. In other words, there is a thread safety guarantee “at the bottom”.

So what exactly does “bottom” mean?

Which brings us to the implementation of enumerations. This section can be referenced in another of my blog posts that looked in depth at the thread safety and serialization issues of Enumerations in Java.

Enumerations are defined using enum, which, like class, is a Java keyword. Just as class applies a class to class, enum should have an enum class to enum.

By decompilating defined enumerations, we can see that enumerations, after being compiled by JavAC, are transformed into definitions like Public Final Class T extends Enum.

Furthermore, each enumeration item in an enumeration is defined static. Such as:

public enum T {
    SPRING,SUMMER,AUTUMN,WINTER;
}
Copy the code

The decompiled code is:

Public final class extends Enum {public static final T extends Enum; public static final T SUMMER; public static final T AUTUMN; public static final T WINTER; private static final T ENUM$VALUES[]; static { SPRING = new T("SPRING", 0); SUMMER = new T("SUMMER", 1); AUTUMN = new T("AUTUMN", 2); WINTER = new T("WINTER", 3); ENUM$VALUES = (new T[] { SPRING, SUMMER, AUTUMN, WINTER }); }}Copy the code

Those of you familiar with the JVM’s classloading mechanism should be familiar with this section. Static properties are initialized after the class is loaded. We looked at Java’s ClassLoader mechanism in depth (source level) and Java class loading, linking, and initialization in depth. When a Java class is actually used for the first time, the static resource is initialized, the Java class is loaded, and the initialization process is thread-safe (because the virtual machine uses the ClassLoader loadClass method to load enumerated classes, which is thread-safe using synchronized code blocks). Therefore, creating an enum type is thread-safe.

That is, an enumeration we define will be loaded and initialized by the virtual machine the first time it is actually used, and this initialization is thread-safe. As we know, to solve the concurrency problem of singletons, the main solution is the thread safety problem in the initialization process.

So, because of the above features of enumerations, singletons implemented by enumerations are inherently thread-safe.

Enumerations solve the problem of deserialization breaking singletons

As mentioned earlier, there is a problem with singletons implemented using double check locks. It is possible that singletons can be broken by serialized locks. See singletons and serialization for more details.

So why do enumerations have an inherent advantage over serialization? The answer can be found in the Java Object Serialization Specification. The serialization of enumerations is specified as follows:

In serialization, Java simply prints the enumeration object’s name property into the result. In deserialization, Java uses the java.lang.Enum valueOf method to find the enumeration object by name. At the same time, the compiler does not allow any customization to this serialization mechanism, so the writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods are disabled.

Normal Java class deserialization uses reflection to call the class’s default constructor to initialize the object. So, even if the constructor in a singleton is private, it will be destroyed by reflection. Since the deserialized object is new again, this breaks the singleton.

However, deserialization of enumerations is not done by reflection. As a result, singleton destruction due to deserialization does not occur. This section also provides a more detailed look at the thread-safety and serialization issues of Enumerations in Java, as well as some code for those interested.

conclusion

Of all the singleton implementations, enumerations are one of the simplest ways to write code. The code is concise because Java gives us the enum keyword, so we can easily declare an enumeration without worrying about thread safety during initialization. Enumeration classes are thread-safe to initialize when loaded by the virtual machine.

In addition, in terms of serialization, Java specifies that the serialization and deserialization of enumerations are specifically tailored. This avoids the problem of singletons being broken due to reflection during deserialization.