Whether you’re a freshman in college or a CRUD with 2-3 years of experience, that means you’ll be asked these questions for at least 3 years. Take the time to get to the bottom of them. The best way to get rid of your fear is to face him, Ollie! This series is my notes and summary of the learning process, and provides debugging code for everyone to play with

The chapter reviews

1. How to prove that volatile does not guarantee atomicity

2. Briefly describe what is the happen-before principle

3. Briefly describe the use of volatile

Please review the above questions by yourself, or the previous chapter if you have any questions

In this chapter the feed

This chapter mainly focuses on the singleton design pattern. Through the application of seven singleton design patterns, the related characteristics of volatile and synchronized mentioned in the previous chapters are further consolidated. (As always, students who are familiar with this section can choose to direct focus on 👍 to complete this chapter!)

This chapter code download

The singleton pattern

The singleton design pattern is one of the most commonly used of goF’s 23 design patterns. This type of design pattern is the creation pattern, which provides the best way to create objects. This pattern involves a single class that is responsible for creating its own objects while ensuring that only a single object is created. This class provides a way to access its unique objects directly, without instantiating the objects of the class.

Note:

  • A singleton class can have only one instance.

  • A singleton class must create its own unique instance.

  • The singleton class must provide this instance to all other objects.

According to these three features, let’s look at the seven implementation methods of singleton design pattern and their advantages and disadvantages from three dimensions of thread safety, high performance and whether lazy loading is supported.

One, hungry

The implementation is as follows:

public class Singleton { private byte[] date = new byte[1024]; private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; }}Copy the code

Hungry, the characteristics of the Chinese type is to be able to guarantee under the multithreading provides the instance is unique, and because at the time of class loading has been completed a Singleton object initialization, so the external call getInstance () ‘s efficiency is relatively high. However, this method already completes the class instance initialization when the class is loaded, so if the instance takes up a lot of space, the result is not pretty. Once a large number of large instances are created using the Hunchman singleton pattern, duang ~ duang ~ cool.

Therefore, hangry is suitable for a class with few member attributes and small resource usage. However, the number of member attributes is relatively small, and the number of resources is relatively small. Once a large number of members are used, the results are uncontrollable. So we prefer to support lazy loading to initialize objects.

Two, lazy loading

The implementation is as follows:

public class SingletonLazy {

  private byte[] date = new byte[1024];

  private static SingletonLazy singletonLazy = null;

  private SingletonLazy() {

  }

  public static SingletonLazy getInstance() {
    if (null == singletonLazy) {
      singletonLazy = new SingletonLazy();
    }
    return singletonLazy;
  }
}
Copy the code

We can see only in the callgetInstance()Method is used to create instance objects. This is the lazy loading mode we talked about earlier. Instance objects are created when used. However, lazy loading created instance objects are not unique in multi-threaded scenarios.As shown in the figure above, when threads A and B are called simultaneouslySingletonLazy.getInstance()When A and B are created, the created instance is not unique.

For those of you who have studied synchronized, using a synchronization strategy for this method ensures that the created instances are unique.

Lazy loading + synchronization strategy

Modify part of the code in the previous section as follows:

Public static synchronized SingletonLazy getInstance() {if (null == SingletonLazy) {SingletonLazy = new SingletonLazy(); } return singletonLazy; }Copy the code

Because of the exclusivity of synchronized, instances created in lazy loading mode can be guaranteed to be unique in multi-threaded scenarios. However, because of the exclusivity, only one thread can obtain the right to use the instance object at a time, so the performance of the program will be greatly reduced.

Fourth, Double – Check

The implementation is as follows:

public class DoubleCheck { private byte[] date = new byte[1024]; private static DoubleCheck doubleCheck = null; Private DoubleCheck() {} public static DoubleCheck getInstance() {if (null == DoubleCheck) {-- (1) synchronized (DoubleCheck. Class) {-- (2) if (null == DoubleCheck) {-- (3) DoubleCheck = new DoubleCheck(); -- (4)}}} return doubleCheck; }}Copy the code

Let’s look at the implementation of getInstance() in detail. First, null == doubleCheck is determined. If it is true, it will enter the synchronization module to create the corresponding singleton instance object. Compared with lazy loading + synchronization, this method is more flexible and does not require every thread to wait outside the synchronization block, thus realizing a simple filtering function.

However, this approach can result in null-pointer exceptions in high concurrency scenarios.

Before we get to that question let’s ask the class a question: Is a block of code modified by synchronized necessarily thread-safe?

⚠️ Is this a “fake” synchronized?

Earlier we said that the synchronized keyword locks the monitor lock on the current object, ensuring thread-safety through exclusivity. There is nothing wrong with This, but we need to pay special attention to the concepts This Monitor and Class Monitor.

When we use synchronized in a static method, we actually lock the DoubleCheck. Class Monitor, but when we use the new keyword in a method to create an object, the process is to operate on the instance variable of that object, namely This Monitor, so you can see that our newly created object instance is not actually affected by the synchronized keyword. These two are not the same Monitor lock.

We can use code to verify the difference between this Monitor and class Mointor.

Create thisorClsssMonitor.class service class

Public class ThisOrClsssMonitor {synchronized public static void printA() { System.out.println(" Thread "+ thread.currentThread ().getName()+" printA"); Thread.sleep(2000); System.out.println(" Thread "+ thread.currentThread ().getName()+" printA"); } catch (InterruptedException e) { e.printStackTrace(); }} synchronized public static void printB() { System.out.println(" Thread "+ thread.currentThread ().getName()+" printB"); Thread.sleep(2000); System.out.println(" Thread "+ thread.currentThread ().getName()+" printB"); } catch (InterruptedException e) { e.printStackTrace(); }} synchronized public void printC() { System.out.println(" Thread "+ thread.currentThread ().getName()+" printC"); Thread.sleep(2000); System.out.println(" Thread "+ thread.currentThread ().getName()+" leave printC"); } catch (InterruptedException e) { e.printStackTrace(); }}}Copy the code

Create three thread classes to call this service class

public class ThreadA extends Thread{ @Override public void run() { ThisOrClsssMonitor.printA(); } } public class ThreadB extends Thread{ @Override public void run() { ThisOrClsssMonitor.printB(); } } public class ThreadC extends Thread{ private ThisOrClsssMonitor thisOrClsssMonitor = new ThisOrClsssMonitor(); @Override public void run() { thisOrClsssMonitor.printC(); }}Copy the code

Create a test class to test

public class ThisOrClsssMonitorTest { public static void main(String[] args) { ThreadA threadA = new ThreadA(); threadA.setName("A"); threadA.start(); ThreadB threadB = new ThreadB(); threadB.setName("B"); threadB.start(); ThreadC threadC = new ThreadC(); threadC.setName("C"); threadC.start(); }}Copy the code

Result output:

Thread A enters printA thread C enters printC thread A leaves printA thread C leaves printC thread B enters printB thread B leaves printBCopy the code

It is obvious that thread A and thread C do not run mutually exclusive, but simultaneously enter logical functions, so synchronized of static methods and synchronized of non-static methods do not lock the same Monitor lock. We can test this several times, and we can see that only after thread A has finished executing and left printA can thread B get the class Monitor into its execution logic.


Synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized

The reason why NPE is generated is that the monitor locked by synchronized is the class Monitor, and the monitor lock of the new object instance in our static fast synchronization is caused by this Monitor.

In this case, we need to ensure that our new instance object is thread safe, ** before considering how to satisfy the premise is to consider why this requirement is generated. ** Methodology coming!! .

So let’s look at this code

if (null == doubleCheck) { synchronized (DoubleCheck.class) { if (null == doubleCheck) { doubleCheck = new DoubleCheck(); -- Let's see here (1)}}}Copy the code

If we look at the code at position (1), the reason it’s not thread-safe is because the object creation and assignment process is not thread-safe. In fact, it is the process of object creation. We can roughly divide the process of object creation into the following steps:

1. Whether the class has been loaded, parsed, and initialized

2. The virtual machine allocates memory for new objects (pointer collisions and free lists, which will be covered in the next series of Java virtual machines).

3. The Java VIRTUAL machine must perform necessary Settings on the object

4. Invoke the constructor to initialize the object

After the virtual machine has completed the necessary Settings for the object, the internal Java virtual machine object has been generated, but we have not completed the initialization of some parameters. At this point, if an additional thread reads the object, it will get NULL! = doubleCheck means that the current object has already been created, so if some parameters of the current object are called for logical processing, NPE problems will occur.

Ok, so now that we’ve explained the cause of the problem, we’ve introduced the happen-before principle in the previous section, where we had a constraint on the volatile keyword.

The volatile rule: happen-before for writes to a volatile variable to read to that variable.

The problem is that NPE occurs when a thread reads the NULL value of the object to perform certain business operations, such as null.equals(), before the object is initialized.

Five, Double – Check + Volatile

We modified the double-check section as follows:

//Volatile modifier prevents reordering private static Volatile DoubleCheck DoubleCheck = null;Copy the code

This solves the potential NPE problem with double-check. Although the scheme is very simple, I hope you read and think carefully about the whole process of exploration

If you like it, please like it and follow 😄

6. Hold mode

Use internal static classes to implement the singleton pattern

public class SingleHolder { private byte[] date = new byte[1024]; Private static class Holder {private static class Holder SingleHolder = new SingleHolder(); } public static SingleHolder getInstance() { return Holder.singleHolder; }}Copy the code

I think this implementation is actually a variant version of lazy loading, clever use of the internal static class will only load once feature, not only to ensure the singleton mode of this feature, but also to meet the requirements of lazy loading. This is a more widely used way of implementation, we can use this way of implementation in the usual development oh ~

Seventh, enumeration method

public enum SingleEnum { INSTANCE; private byte[] date = new byte[1024]; SingleEnum(){ System.out.println("INSTANCE will be initialized immediately"); } public static void method(){// SingleEnum getInstance(){return SingleEnum getInstance() INSTANCE; }}Copy the code

This is the exact enum version of the original, taking advantage of the fact that enum classes are not inheritable and can only be loaded once. However, it has a disadvantage that if we write a static method in the enumeration class, then in the process of calling the static method, our enumeration instance will be initialized, which in some cases does not meet the lazy loading characteristics.

Use a test class to solve this problem:

public static void main(String[] args) { SingleEnum.method(); }}Copy the code

Output:

INSTANCE will be initialized immediately
Copy the code

Can see our side has invoked the enumeration of the constructor, or enumeration class initialization has been completed, we enumerated instances has also been created, but the fact is that we use one of the static methods, enumerated instances actually haven’t really applied to our business logic, so this is can not meet the needs of lazy loading.

Classic sayings

Is there any problem that can’t be solved by just one more layer

Let’s throw the enumerator into another class and make it a private internal enumerator of that class. Look at the code first

public class SingleEnum2 { private byte[] date = new byte[1024]; private SingleEnum2() { System.out.println("INSTANCE will be initialized immediately"); } private enum EnumHolder { INSTANCE; private SingleEnum2 singleEnum2; EnumHolder() { this.singleEnum2 = new SingleEnum2(); } private SingleEnum2 getSingleEnum2() { return singleEnum2; }} public static void method(){// INSTANCE is not instantiated} public static SingleEnum2 getInstance() {return EnumHolder.INSTANCE.getSingleEnum2(); }}Copy the code

The idea is to throw an enumeration class into a class, so that static methods that call the class instantiate the object of the class instead of instantiating the instance of our internal, private enumeration class, so we can get lazy loading


Ok ~ this period of consolidation learning to end here, I do not know whether the students have learned these 7 singleton pattern implementation methods? After learning, I believe students have a deeper understanding of synchronized and volatile, like 👍➕ follow ❤️, keep updating ~ ~