This is the 17th day of my participation in Gwen Challenge

Design patterns

The singleton pattern

There is only one instance of a class in the entire software system

  • Hungry (static constant)
  • Hungry (static code block)
  • Lazy (thread unsafe)
  • Lazy (thread-safe, synchronous approach)
  • Lazy (thread-safe, synchronized code blocks)
  • Double check lock
  • Static inner class
  • The enumeration

Hungry (static constant)

package com.wangscaler.singleton;

/ * * *@author wangscaler
 * @date 2021.06.18 11:18
 */

public class Hungryman {
    public static void main(String[] args) {
        StatiConst statiConst1 = StatiConst.getInstance();
        StatiConst statiConst2 = StatiConst.getInstance();
        System.out.println(statiConst2.hashCode());
        System.out.println(statiConst1.hashCode());
    }

    static class StatiConst {
        // After privatization, external cannot be new
        private StatiConst(a) {}private final static StatiConst instance = new StatiConst();

        public static StatiConst getInstance(a) {
            returninstance; }}}Copy the code

Conclusion: Constructing hangry requires attention

  • 1. Privatize the constructorprivate StatiConst() { }
  • 2. Create objects inside the classprivate final static StatiConst instance = new StatiConst();
  • Expose static public methodspublic static StatiConst getInstance() { return instance; }
  • 4. This method will be instantiated at class load time, so the global getInstance will always be one instance, this method is thread safe.
  • If this instance is not used in the project, memory will be wasted.

Hungry (static code block)

package com.wangscaler.singleton;

/ * * *@author wangscaler
 * @date 2021.06.18 11:18
 */

public class Hungryman1 {
    public static void main(String[] args) {
        StatiCodeBlock statiConst1 = StatiCodeBlock.getInstance();
        StatiCodeBlock statiConst2 = StatiCodeBlock.getInstance();
        System.out.println(statiConst2.hashCode());
        System.out.println(statiConst1.hashCode());
    }

    static class StatiCodeBlock {
        // After privatization, external cannot be new
        private StatiCodeBlock(a) {}static {
            instance = new StatiCodeBlock();
        }

        private static StatiCodeBlock instance;

        public static StatiCodeBlock getInstance(a) {
            returninstance; }}}Copy the code

This creates static constants in the same way.

Lazy (thread unsafe)

package com.wangscaler.singleton;

/ * * *@author wangscaler
 * @date2021.06.18 14:06 * /
public class Lazyman {
    public static void main(String[] args) {
        Unsafe instance = Unsafe.getInstance();
        Unsafe instance2 = Unsafe.getInstance();
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
        System.out.println(instance == instance2);
    }

    static class Unsafe {
        private static Unsafe instance;

        private Unsafe(a) {}public static Unsafe getInstance(a) {
            if (instance == null) {
                instance = new Unsafe();
            }
            returninstance; }}}Copy the code

In a single thread this method creates the object only when it is first used. When it has been created, the previously created object is returned. However, in multithreading, it is possible to initialize multiple instances, so this method is not thread safe. In practical development, remember not to use this method.

Lazy (thread-safe, synchronous approach)

package com.wangscaler.singleton;

/ * * *@author wangscaler
 * @date2021.06.18 14:06 * /
public class Lazyman1 {
    public static void main(String[] args) {
        Synchronizationmethod instance = Synchronizationmethod.getInstance();
        Synchronizationmethod instance2 = Synchronizationmethod.getInstance();
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
        System.out.println(instance == instance2);
    }

    static class Synchronizationmethod {
        private static Synchronizationmethod instance;

        private Synchronizationmethod(a) {}public static synchronized Synchronizationmethod getInstance(a) {
            if (instance == null) {
                instance = new Synchronizationmethod();
            }
            returninstance; }}}Copy the code

Changing the getInstance method to a synchronized method would block each thread and have to wait for the previous thread to finish accessing it, which would be inefficient.

Lazy (thread-safe, synchronized code blocks)

package com.wangscaler.singleton;

import sun.misc.JavaAWTAccess;

/ * * *@author wangscaler
 * @date2021.06.18 14:06 * /
public class Lazyman2 {
    public static void main(String[] args) {
        Synchronizationcodeblock instance = Synchronizationcodeblock.getInstance();
        Synchronizationcodeblock instance2 = Synchronizationcodeblock.getInstance();
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
        System.out.println(instance == instance2);
    }

    static class Synchronizationcodeblock {
        private static Synchronizationcodeblock instance;

        private Synchronizationcodeblock(a) {}public static Synchronizationcodeblock getInstance(a) {
            if (instance == null) {
                synchronized (Lazyman2.class) {
                    instance = newSynchronizationcodeblock(); }}returninstance; }}}Copy the code

Putting synchronization in a block of code is the same as lazy (thread-unsafe) approach. It doesn’t work, and it still creates multiple instances, so it can’t be used.

Double check lock

package com.wangscaler.singleton;

public class Doublechecklock {
    public static void main(String[] args) {
        Doublecheck doublecheck = Doublecheck.getInstance();
        Doublecheck doublecheck1 = Doublecheck.getInstance();
        System.out.println(doublecheck.hashCode());
        System.out.println(doublecheck1.hashCode());
        System.out.println(doublecheck == doublecheck1);
    }

    static class Doublecheck {
        private static volatile Doublecheck instance;

        private Doublecheck(a) {}public static Doublecheck getInstance(a) {
            if (instance == null) {
                synchronized (Doublecheck.class) {
                    if (instance == null) {
                        instance = newDoublecheck(); }}}returninstance; }}}Copy the code

Synchronized (Doublecheck. Class) is a synchronized (instance == null) lock, and synchronized (instance == null) is a synchronized (instance == null) lock. If three threads pass the first checkpoint, when the first thread gets the key to open the door, the remaining two have to wait. When the first thread creates the object through the second checkpoint and flusher it to memory, the remaining threads come in and cannot pass the second checkpoint

You can see the use of the keyword volatile, which prevents instruction reordering, which was problematic prior to Java5.

Here instance = new Doublecheck(); There are actually three operations.

1. Allocate memory for instance

2. Call the constructor to initialize the variable

3. Point the object to the allocated memory space

These three operations will result in instruction rearrangement, namely 1-2-3/1-3-2.

  • So what is order reordering?

Instruction rearrangement: The processor may optimize the input code to improve the efficiency of the program. It does not ensure that the execution order of each statement in the program is the same as that in the code, but it ensures that the final execution result of the program is the same as that in the code order.

In short, the code is not necessarily run in the order of coding, but it can rearrange instructions without dependencies according to the data dependencies between instructions to ensure consistency of results and improve efficiency.

If an instruction is rearranged, i.e. execute 3 first and then execute 2, then a new thread will come in after execute 3, but 2 has not executed yet, then the new thread will get the object (execute 3, then the object is not null), and there will be a problem.

  • Why is this happening?

The Java memory model specifies that all variables are stored in main memory (similar to physical memory) and that each thread has its own working memory (similar to caching). Common shared variables do not guarantee visibility, because it is uncertain when a common shared variable will be written to main memory after modification. When another thread reads a common shared variable, it may still have the old value in memory, so visibility cannot be guaranteed.

Using volatile not only prevents instruction reordering, but immediately updates the changed value to main memory, where it can be read when another thread needs to read it.

The resources

  • Java concurrent programming: Volatile keyword resolution
  • How do I write the singleton pattern correctly