In-depth understanding of the implementation of several singleton patterns

Hungry

The hungry-style singleton implementation means that the class is created and initialized when the class is loaded, so the instance creation process is thread-safe

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private static final IdGenerator instance = new IdGenerator();
    private IdGenerator(){}
    public static IdGenerator getInstance(){
        return instance;
    }
    public long getId(){
        return id.incrementAndGet();
    }
}

But the hungry Chinese style has a disadvantage. It does not support delayed loading, which means that the instance has been created before use. If it takes up too many resources but does not use it, it will cause a certain amount of waste. Specific circumstances determine whether to use this method. However, some people say that if it takes a long time, then loading it when it is used will affect performance, and it is difficult for the hungry.

AtomicLong is an atomic variable type that provides thread-safe atomic operations, which ensures that in a multi-threaded environment, there will be no duplicate IDs when obtaining the id.

The constructor in the code is decorated with a private modifier, ensuring that external code cannot initialize the IdGenerator class through the constructor.

The above is a simple unique incremental ID number generator, using the hungry Chinese style singleton implementation mode, the instance instance is defined as a static constant, we know that when running a class, we must first load it into the JVM, class The loading process is divided into three stages, namely: loading, linking and initialization, where the linking stage is divided into three steps: verification, preparation and resolution, in which the static of the class or interface will be created in the preparation step Variables and initialize the initial values ​​of static variables.

Lazy

Above we said that the hungry Chinese style does not support delayed loading, then the lazy style supports delayed loading, and the lazy style is implemented by adding a lock to the method of obtaining an instance.

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private static IdGenerator instance;
    private IdGenerator(){}
    
    public static synchronized IdGenerator getInstance(){
        if (instance == null){
            instance = new IdGenerator();
        }
        return instance;
    }
    public long getId(){
        return id.incrementAndGet();
    }
}

The result of locking is reduced performance. If this singleton is used frequently, the performance problem will be more serious, and we need to consider another way to achieve it.

Double detection

The single-instance implementation method of double detection makes up for the shortcomings of the hungry and lazy styles above: the problem of delayed loading and low performance. The specific implementation method is to determine whether it has been created before acquiring the instance. Direct return, this is the first detection, if not, enter the synchronization block, enter the synchronization block and then determine whether the instance already exists, if it exists, then return directly, this is the second detection, if it does not exist, then synchronize To create an instance.

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private static IdGenerator instance;
    private IdGenerator(){
        // 初始化代码
    }
    public static IdGenerator getInstance(){
        if (instance == null){
            synchronized (IdGenerator.class){ // 这里指明synchronized保护的是当前的类对象
                if (instance == null){
                    instance = new IdGenerator();
                }
            }
        }
        return instance;
    }
    public long getId(){
        return id.incrementAndGet();
    }
}

The lazy implementation method must enter the synchronization code every time the instance is acquired, which will cause multiple acquisition locks to release the lock, resulting in performance loss, but the double detection actually only needs to synchronize the instance creation once, and then acquire It is not necessary to enter the synchronization block code in the instance, which greatly improves the performance.

Static inner class

The implementation of the static inner class is a simpler implementation than double detection, and it ensures both performance and delayed loading.

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private IdGenerator(){
        // 初始化代码
    }
    private static class SingletonHolder{
        private static final IdGenerator instance = new IdGenerator();
    }
    
    public static IdGenerator getInstance(){
        return SingletonHolder.instance;
    }
    public long getId(){
        return id.incrementAndGet();
    }
}

SingletonHolder is a static inner class. Using privat can make the inner class completely hidden from the outside world. When the outer class IdGenerator is loaded, no instance will be created. Only when the getInstance method is called will the SingletonHolder be loaded and an instance created, so that it has In addition to the hungry-style security features, it also has the feature of lazy loading.

enumerate

The implementation of enumeration is to use the characteristics of the java enumeration type itself to ensure the thread safety of instance creation and the uniqueness of the instance

public enum IdGenerator {
    INSTANCE;
    private AtomicLong id = new AtomicLong(0);
    public long getId(){
        return id.incrementAndGet();
    }
    public static void main(String[] args) {
        IdGeneratorEnum.INSTANCE.getId();
    }
}

It can be seen that the implementation of enumeration is the most concise, and the jvm guarantees thread safety and single instance. It can also effectively prevent serialization and deserialization from creating multiple instances and using reflection to create multiple instances. After compiling the enumerated type into a class file, then decompile,

public final class IdGenerator extends java.lang.Enum<IdGenerator> {
  public static final IdGenerator INSTANCE;
  public static IdGenerator[] values();
  public static IdGenerator valueOf(java.lang.String);
  public long getId();
  public static void main(java.lang.String[]);
  static {};
}

The decompiled enumeration class actually inherits the Enum class. INSTANCE is a static constant. It seems to be back to the hungry Han type, but it has more lazy loading and more concise features.

Welcome everyone to my blog Chou Chou, there are more details about the test of combat Oh! !

Guess you like

Origin www.cnblogs.com/zyjimmortalp/p/12671892.html