Write the perfect singleton pattern in Java

Source: Internet
Author: User
Tags static class volatile eventbus

1. Preface

The singleton (Singleton) should be the most familiar design pattern for developers, and it seems to be the easiest to implement-basically every developer can write it out-but is that really the case?

As a Java developer, you may feel that you have enough knowledge of the singleton pattern. I do not want to alarmist say there must be something you do not know-after all, my own understanding is really limited, but what exactly do you know the extent of it? Look down, let's talk about it.

2. What is a single case?

The class of a singleton object must ensure that only one instance exists-this is the definition of a singleton on Wikipedia, which can also be used as a standard for testing code that intends to implement a singleton pattern.

The realization of Singleton can be divided into two categories---------------The A hungry man and the

    • Lazy: Refers to a global singleton instance that was built when it was first used.

    • A hungry man: refers to a global singleton instance that is built when the class is loaded.

From their differences can also be seen, the daily use of more should be lazy-type singleton, after all, on-demand loading to achieve the maximization of resources use it ~

3. Lazy type Single case

Let's take a look at the way the lazy Singleton is implemented.

3.1 Simple version

Look at the simplest notation, version 1:

Version 1public class Single1 {    private static Single1 instance;    public static Single1 getinstance () {        if (instance = = null) {            instance = new Single1 ();        }        return instance;}    }

Or, further, change the constructor to private, which prevents the external class from being called.

Version 1.1public class Single1 {    private static Single1 instance;    Private Single1 () {} public    static Single1 getinstance () {        if (instance = = null) {            instance = new Single1 () ;        }        return instance;}    }

I seem to remember how the school textbooks were taught? --before each acquisition of instance, if the instance is empty, the new one comes out, otherwise it returns the existing instance directly.

This is not a problem in most of the time. The problem is that when multiple threads are working, if more than one thread is running to if (instance = = null) and all are judged null, then two threads will each create an instance-this is not a singleton.

3.2 Synchronized version

Since it may cause problems due to multithreading, then add a sync lock!

The modified code is as follows, with respect to Version1.1, just add a synchronized to the method signature:

Version 2 public class Single2 {    private static Single2 instance;    Private Single2 () {} public    static synchronized Single2 getinstance () {        if (instance = = null) {            instance = new Single2 ();        }        return instance;}    }

OK, after adding the synchronized keyword, the getinstance method will be locked. If there are two threads (T1, T2) executing to this method at the same time, one of the threads T1 get the synchronization lock, continue execution, while the other thread T2 waits, when the T1 executes getinstance (after the null judgment, object creation, and return value is completed), The T2 thread does not execute. -so this side code also avoids Version1, which can occur because multiple instances of multithreading result in a situation.

However, there is also a problem with this notation: locking the Gitinstance method avoids multiple instance problems that may occur, but forcing all threads except T1 to wait can actually negatively affect the execution efficiency of the program.

3.3 Double check (double-check) version

The efficiency of the Version2 code relative to the VERSION1D code is actually to solve the 1% chance problem and use a 100% shield. There is an optimization idea, that is, the 100% appearance of the shield, but also to 1% of the chance to appear, so that it will only appear in a number of instances may cause the occurrence of the place.

-Is there such a way? Of course there is, the improved code Vsersion3 as follows:

Version 3 public class Single3 {    private static Single3 instance;    Private Single3 () {} public    static Single3 getinstance () {        if (instance = = null) {            synchronized (Single3.clas s) {                if (instance = = null) {                    instance = new Single3 ();}}        }        return instance;}    }

This version of the code looks a bit complicated, note that there are two times if (instance = = null) judgment, this is called "double check Double-check".

    • The first if (instance = = null), in fact, is to solve the efficiency problem in Version2, only when instance is null, only to enter the synchronized code snippet-greatly reducing the odds.

    • The second if (instance = = NULL) is the same as Version2 to prevent situations where multiple instances may occur.

-This code looks flawless.

......

......

......

--Of course, just "looks", there is a small probability that there is a problem.

This makes it clear why there may be a problem here, first of all, we need to figure out several concepts: atomic manipulation, command rearrangement.

Knowledge Point: What is atomic manipulation?

In a nutshell, atomic manipulation (atomic) is an inseparable operation, in a computer, that is, an operation that is not interrupted because of a thread dispatch.

For example, a simple assignment is an atomic operation:

m = 6; It's an atomic operation.

If the original value of M is 0, then for this operation, either the execution succeeds M becomes 6, either m or 0 is not executed, and there is no intermediate state such as m=3-even in a concurrent thread.

Instead, declaring and assigning a value is not an atomic operation:

int n = 6; This is not an atomic operation.

For this statement, there are at least two operations:

① declaring a variable n

② assigns a value of 6 to N

--This will have an intermediate state: The variable n has been declared but has not yet been assigned a state.

-thus, in multi-threading, because of the uncertainty of the order in which threads are executed, if two threads use M, it can lead to unstable results.

Knowledge Point: What is command rearrangement?

In short, some of the optimizations that the computer will make to improve execution efficiency may adjust the order in which some statements are executed without affecting the final result.

For example, this section of code:

int A;   Statement 1 a = 8;   Statement 2int B = 9;     Statement 3int C = a + B; Statement 4

Normally, for sequential structures, the order of execution is from top to bottom, or 1234.

However, because of the reason for the command reflow, the actual execution order may become 3124 or 1324, because it does not affect the final result.

Since statements 3 and 4 do not have an atomic problem, statement 3 and statement 4 may also be split into atomic operations and then re-queued.

-in other words, for non-atomic operations, the split atomic operations may be rearranged in order of execution without affecting the final result.

OK, after understanding the concept of atomic manipulation and command rearrangement, we'll continue to look at the Version3 code problem.

The following paragraph is copied directly from Chenhao's article (single Instance singleton design mode):

The main reason is Singleton = new Singleton (), this is not an atomic operation, in fact in the JVM this sentence probably did the following 3 things.

1. Allocating memory to Singleton

2. Call Singleton's constructor to initialize the member variable, forming an instance

3. Point the Singleton object to the allocated memory space (singleton is not NULL if this step is performed)

However, there is an optimization of command reordering in the JVM's immediate compiler. In other words, the order of the second and third steps above is not guaranteed, and the final order of execution may be 1-3-2. If it is the latter, then after 3 execution, 2 is not executed, before the thread two is preempted, then instance is non-null (but not initialized), so the thread two will return directly to instance, then use, and then logically error.

To explain a little bit more, that is, because there is an intermediate state where "instance is not already null but still not initialized", and this time, if there are other threads just running to the first layer if (instance = = null) here, The instance read here is no longer null, so the instance of this intermediate state is used, which creates a problem.

The key here is that the thread T1 writes to instance is not completed, and the thread T2 reads the operation.

3.4 Ultimate Version: Volatile

For Version3 possible problems (of course, this probability is very small, but after all, there is a ~), the solution is: only need to give instance's statement plus the volatile keyword, Version4 version:

Version 4 public class Single4 {    private static volatile Single4 instance;    Private Single4 () {} public    static Single4 getinstance () {        if (instance = = null) {            synchronized (Single4.clas s) {                if (instance = = null) {                    instance = new Single4 ();}}        }        return instance;}    }

One function of the volatile keyword is to prohibit the command rearrangement, after declaring the instance as volatile, there will be a memory barrier to its write operation (what is the memory barrier?). ), so that the read operation is not invoked until its assignment is complete.

Note: Volatile blocking does not Singleton = new Singleton () This sentence of the internal ["] command rearrangement, but to ensure that the completion of a write operation (["] ") does not invoke the read operation (if (instance = = null)).

--It also completely prevents problems in the Version3 from happening.

--Well, there's nothing wrong with that now, is it?

......

......

......

All right, don't be nervous, it's no problem. In the famous Eventbus, the entrance method Eventbus.getdefault () is implemented in this way.

......

......

......

However, the need to pick a bit of the words can be picked out, is the wording of some complex, not elegant, concise.

4. A hungry man type single case

Let's talk about the A hungry man type of single case.

As mentioned above, a hungry man-type Singleton refers to the implementation of a global singleton instance that is built when the class is loaded.

Since the process of class loading is performed by the ClassLoader (ClassLoader), which is also guaranteed to be synchronous by the JVM, there is an inherent advantage in this approach-the ability to immunize many problems caused by multithreading.

4.1 A hungry man-type single-case implementation method

The implementation of the A Hungry man-type single example is as follows:

A hungry man implement public class Singleb {    private static final Singleb INSTANCE = new Singleb ();    Private Singleb () {} public    static Singleb getinstance () {        return INSTANCE;    }}

It's basically perfect for a a hungry man-style single-case notation.

So its disadvantage is only the A hungry man of the shortcomings of the single example itself-because the instance initialization is carried out at class loading, and the class is loaded by the ClassLoader to do, so the developer originally for its initialization time is difficult to accurately grasp:

    1. may be due to initialization too early, resulting in a waste of resources

    2. If the initialization itself relies on some other data, it is difficult to ensure that other data is ready before it is initialized.

Of course, this implementation is also good if the required singleton occupies little resources and does not depend on other data.

Knowledge Point: When is the class loaded?

As mentioned earlier, when a singleton is instantiated when the class is loaded, when is the class loaded?

Not strictly speaking, there are so few conditions that will trigger a class to be loaded:

1. When you new an object

2. When you create an instance of it using reflection

3. When a subclass is loaded, if the parent class is not loaded, the parent class is loaded first

4. The main class that executes when the JVM starts is loaded first

5. Some other ways of achieving

5.1 Effective Java intrinsic static inner class

In the first edition of the book "Effective Java," one of the following is recommended:

Effective Java first edition recommended public class Singleton {    private static class Singletonholder {        private static final Sing Leton INSTANCE = new Singleton ();    }    Private Singleton () {} public    static final Singleton getinstance () {        return singletonholder.instance;    }}

This is a very ingenious notation:

    • For the inner class Singletonholder, it is a a hungry man-type implementation of the Singleton, when the Singletonholder initialization will be classloader to ensure synchronization, so instance is a true single case.

    • At the same time, since Singletonholder is an inner class that is only used in the getinstance () of the Singleton of the outer class, the time it is loaded is when the getinstance () method is called the first time.

-It uses ClassLoader to ensure synchronization while allowing developers to control the timing of class loading. From the inside is a a hungry man type of singleton, but from the outside, it is really a lazy implementation.

It's gadget.

5.2 Effective Java 2--Enumeration

You think you're done here? No, no, because the great God found another way.

The author of "Effective Java" in the second edition of this book also recommended another way to see the code directly:

Effective Java second Edition recommended public enum singleinstance {    INSTANCE;    public void Fun1 () {         //do Something    }}//using SINGLEINSTANCE.INSTANCE.FUN1 ();

Did you see that? This is an enumeration type ... Not even class, minimalist.

Because the process of creating an enumeration instance is thread-safe, there is no synchronization problem with this notation.

The author's evaluation of this method:

This notation is functionally similar to the common domain approach, but it is more concise and provides a free serialization mechanism that absolutely prevents this from being instantiated, even in the face of complex serialization or reflection attacks. Although this method is not widely used, single-element enumeration types have become the best way to implement Singleton.

Enumeration Examples This approach comes out, and many analysis articles say it is the perfect way to achieve a single case-it's super simple and can solve most of the problems.

But I personally think that this method is excellent, but it is still not perfect-for example, in a scenario that requires inheritance, it does not apply.

6. Summary

OK, see here, you still think the singleton mode is the simplest design mode? Look back at your previous code in the singleton implementation, think it is invulnerable?

Perhaps we are in actual development, the implementation of the singleton is not so strict requirements. For example, if I can guarantee that all getinstance are on one thread, the first simplest textbook method is enough. For example, sometimes my singleton becomes a lot of cases and it may not have much effect on the program ...

However, if we can learn more about the details, then if one day the program has some problems, we can at least one more point to troubleshoot the problem. Solve the problem earlier, you can go home early for dinner ...

-and, the perfect solution is not there, there is a "degree" problem in any way. For example, you think the code is impeccable, but because you use the Java language, may classloader some bugs ah ... Your code who runs on the JVM may have bugs in the JVM itself ... Your code is running on the phone, there may be a problem with the phone system ... You live in this universe, maybe the universe itself has some bugs ah ...

So, try to do the best you can do.

Reproduced in original: https://www.cnblogs.com/dongyu666/p/6971783.html

Write the perfect singleton pattern in Java

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.