Full han mode

Han Dynasty is the most varied singleton pattern. Starting from The Han Dynasty, we gradually understand the problems that need to be paid attention to when realizing the singleton pattern through its variation.

Basic full Han

Full han, that is, have eaten full, do not worry to eat again, when hungry to eat. So he does not initialize the singleton until it is used for the first time, i.e. lazy loading.

UnThreadSafe public class Singleton1 {private static Singletion1 singleton = null; private Singleton1() { } public static Singleton1 getInstance() { if (singleton == null) { singleton = new Singleton1();  } return singleton; }}Copy the code

The core of the full Han model is lazy loading. The advantage is that the startup speed is faster, saving resources, until the instance is accessed for the first time, need to initialize the singleton; The minor downside is that it’s hard to write, the major downside is that it’s not thread safe, and if statements have race conditions.

Writing is not a big problem, readable ah. Therefore, in the single-thread environment, basic han is the author’s favorite writing method. But in a multi-threaded environment, the base is completely unavailable. The following variants all attempt to address the problem of unsafe base – loaded threads.

Full Han – Variant 1

The grossest offense is to modify the getInstance() method with the synchronized keyword, which is perfectly thread-safe.

ThreadSafe public class Singleton1_1 {private static singleton = null; private Singleton1() { } public synchronized static Singleton1_1 getInstance() { if (singleton == null) { singleton = new Singleton1_1 (); } return singleton; }}Copy the code

Variant 1 has the benefit of being simple to write and absolutely thread-safe; The downside is that concurrent performance is extremely poor, and in fact completely degraded to serial. Singletons need to be initialized only once, but even then synchronized locks cannot be avoided, making getInstance() an entirely serial operation. This parameter is recommended when performance is not sensitive.

Full Han – Variant 2

Variant 2 was the “infamous” DCL 1.0.

To solve the problem that the Lock still cannot be avoided after the singleton initialization in variant 1, variant 2 sets another layer of check on the outer layer of variant 1 and adds the inner layer of synchronized check, namely the so-called “Double check Lock” (REFERRED to as DCL).

ThreadSafe public class Singleton1_2 {private static Singleton1_2 singleton = null; private Singleton1() { } public static Singleton1_2 getInstance() { //may get half object if (singleton == null) { synchronized(Singleton1_2.class) { if (singleton == null) { singleton == new Singleton1_2(); } } } return singleton; }}Copy the code

The core of variant 2 is the DCL, and it looks as if variant 2 has achieved the desired effect: lazy loading + thread-safe. Unfortunately, as noted in the comments, the DCL is still thread-unsafe, and you can get “half-object” or “partially initialized” problems due to instruction reordering. After looking at variant 3 in detail, you can refer to the following article, which is not repeated here.

Monkeysayhi. Making. IO / 2016/11/29 /…

Full Han – Variant 3

Variant 3 is specifically for variant 2 and is called DCL 2.0.

In response to variant 3’s “half-object” problem, variant 3 added the volatile keyword on instance, as described above.

ThreadSafe Public class Singleton1_3 {private static volatile Singleton1_3 singleton = null; public int f1 = 1; Public int f2 = 2; private Singleton1_3() { } public static Singleton1_3 getInstance() { if (singleton == null) { synchronized (Singleton1_3.class) { // must be a complete instance if (singleton == null) { singleton = new Singleton1_3(); } } } return singleton; }}Copy the code

In multi-threaded environments, variant 3 is more suitable for performance-sensitive scenarios. But as we’ll see later, there are ways to break singletons, even if thread-safe.

There are, of course, many ways to prevent partial initialization in ways similar to volatile. Readers are encouraged to read about the memory barrier, but pretending to do so is not recommended during the interview.

The hungry mode

As opposed to the full, the hungry are hungry and just want to eat as soon as possible. So he initializes the singleton at the earliest opportunity, when the class is loaded, and returns it directly when it is accessed later.

ThreadSafepublic class Singleton2 {private static final Singleton2 singleton = new Singleton2(); private Singleton2() { } public static Singleton2 getInstance() { return singleton; }}Copy the code

Hangry has the advantage of being inherently thread-safe (thanks to class loading), super simple to write, and no latency to use; The downside is that it can be a waste of resources if the singleton is never used after the class is loaded.

Notably, there was no difference in performance between hungry and full subjects in the single-threaded environment. However, in multi-threaded environment, the performance of hungry people is better because full people need to lock.

Holder model

We want to take advantage of the convenience and thread-safety of static variables in hangry mode; And hope to avoid resource waste through lazy loading. Holder mode meets these two requirements: the core is still static variables, which are convenient and thread-safe; Lazy loading is indirectly implemented by holding real instances in the static Holder class.

ThreadSafepublic class Singleton3 {private static class SingletonHolder {private static final Singleton3  singleton = new Singleton3(); } private Singleton3() { } public static Singleton3 getInstance() { return SingletonHolder.singleton; }}Copy the code

Enumeration mode

Using enumerations to implement singleton patterns is quite handy, but readability is nonexistent.

Basic enumeration

Enumerating static member variables as instances of singletons:

ThreadSafepublic enum Singleton4 {SINGLETON; }Copy the code

Ugly but useful grammar candy

Java enumerations are an “ugly but useful syntactic sugar.”

The nature of the enumerated singleton pattern

By decomposing to open the syntax sugar, you can see the essence of enumerated types, simplified as follows:

ThreadSafepublic class extends Enum<Singleton4> {ThreadSafepublic class extends Enum<Singleton4> {... public static final Singleton4 SINGLETON = new Singleton4(); . }Copy the code

Essentially the same as hanhan-mode, the only difference is the public static member variables.

Implement some trick with enumerations

This part has nothing to do with singletons and can be skipped. If you choose to read, be aware of the fact that while enumerations are quite flexible, they can be difficult to use properly. A simple enough example is the TimeUnit class, which is recommended if you have time.

As you saw above, the essence of an enumerated singleton is still a normal class. In fact, we can add anything a normal class can do to an enumerated singleton. The key is the initialization of the enumerated instance, which can be interpreted as instantiating an anonymous inner class. To make it more obvious, we define a normal private member variable, a normal public member method, and a public abstract member method in Singleton4_1, as follows:

// enumeration // ThreadSafepublic enum SINGLETON("enum is the easiest SINGLETON pattern, but not the most readable") { public void testAbsMethod() { print(); System.out.println("enum is ugly, but so flexible to make lots of trick"); }}; private String comment = null; Singleton4_1(String comment) { this.comment = comment; } public void print() { System.out.println("comment=" + comment); } abstract public void testAbsMethod(); public static Singleton4_1 getInstance() { return SINGLETON; }}Copy the code

Thus, each enumeration instance in the enumeration class Singleton4_1 not only inherits the member method print() of the parent Singleton4_1, but must also implement the abstract member method testAbsMethod() of the parent Singleton4_1.

conclusion

The above analysis ignores reflection and serialization. Through reflection or serialization, we can still access private constructors and create new instances that break the singleton pattern. At this point, only enumeration patterns provide natural protection against this problem. Reflection and serialization are not well understood by the author, but the basic principles are not difficult and can be implemented manually in other modes.

Let’s continue to ignore reflection and serialization for a quick summary:

implementation

The key point

Waste of resources

Thread safety

Multithreaded environments are sufficiently optimized for performance

Based on full han

Lazy loading

no

no

Han Dynasty variant 1

Lazy loading, synchronization

no

is

no

Han Dynasty variant 2

Lazy loading, DCL

no

no

Han Dynasty variant 3

Lazy loading, DCL, volatile

no

is

is

The hungry

Static variable initialization

is

is

is

Holder

Static variable initialization, holder

no

is

is

The enumeration

Enumeration nature, static variable initialization

no

is

is