Implementation of snowflake algorithm in singleton mode

Posted by Popgun on Sun, 23 Jan 2022 14:02:09 +0100

    Snowflake algorithm

    The snowflake algorithm is suitable for generating globally unique numbers, such as database primary key id, order number, etc

    As for why it is called snowflake algorithm, it is because scientists believe that there are no two identical snowflakes in nature through research. Therefore, this algorithm is named after snowflakes and emphasizes that the numbers it generates will not be repeated

    The number generated by snowflake algorithm is 64bit in total, which is just the maximum range of long in java

    The snowflake algorithm is represented by 64 bit binary numbers

    In binary, the first bit is a sign bit, which represents a positive or negative number. A positive number is 0 and a negative number is 1

    Because negative numbers are not required to generate unique numbers, the first digit is always 0, which is equivalent to useless

    41 bits are used to represent the timestamp, which is the millisecond difference between the current time and the specified time.

    For example, the specified time is 2021-06-30 11:07:20, and the snowflake algorithm is called after one second, so the millisecond difference is 1000.

    41 bit binary can represent 241-1 positive numbers. In theory, so many millisecond differences can be used for 69 years.

    (241-1) / (1000∗60∗60∗24∗365)≈69.73

    With the IT industry updating day by day, few programs can last 69 years. So, enough

    The machine number is represented by 10 digits. In a distributed environment, it supports up to 210 = 1024 machines

    The serial number is represented by 12 bits. In the same millisecond, the concurrency is controlled by the serial number on the same machine. 212 = 4096 concurrent messages are allowed in the same millisecond

    To sum up, even if your program has 1024 loads in a distributed environment, and the concurrency per millisecond of each load is 4096, the unique number generated by the snowflake algorithm will not be repeated, and the algorithm is powerful

    The above is the snowflake algorithm based on binary, which is more obscure and difficult to understand, and it is not conducive to the content we will discuss next

    Therefore, we modify the snowflake algorithm as follows

    15 characters are used to represent the time string. For example, at 14:52:30 seconds and 226 milliseconds on June 30, 2021, it can be expressed as 210630145230226. This means that it is more readable and will not be repeated for a hundred years

    The machine number is represented by two digits, which can support up to 100 machines

    The serial number is represented by two digits, and 100 concurrent messages are supported within one millisecond

    Next, implement our adapted algorithm in code (here are pictures, and the source code will be attached at the end of the article)

    This realizes our adapted snowflake algorithm. However, when you think about it, there are concurrency problems in the code

    When two threads execute this code at the same time, the unique number obtained may be repeated

    This is because thread A is suspended when it reaches A line and has not had time to modify the value of lastTime. For example, thread A is suspended when it executes to this line

    At this time, thread B starts to execute. If it determines whether lastTime and nowTime are equal, thread B will continue to execute and obtain a number

    Then, thread A is called to continue execution and get A number. At this time, the numbers obtained by the two threads are repeated

    You can use the synchronized keyword of java to change concurrency to synchronization

    When synchronized is added, when thread A is executing in the method, thread B can only wait outside the method and cannot execute in it, which solves the concurrency problem mentioned above

    However, there is another problem.

    We all know that synchronized is only effective for the same instance. When there are multiple instances, there is no control between multiple instances

    Once multiple instances are generated, the numbers generated between multiple instances may be repeated

    So we can't let the object of this class produce multiple instances, we can only let it always have only one instance

    At this point, the first thing we think of is the singleton mode.

    The biggest feature of singleton mode is that there is only one instance at most in any case, so it is very appropriate to use singleton mode to solve this problem

    Let's talk about how the singleton mode ensures singleton. If you want to ensure singleton, you can't let others create instances casually.

    The best way is to privatize the constructor and make it private. After privatization, only this class can create its own instance, and other classes do not have the permission to call the constructor of this class

    If this class creates only one instance, it is singleton

    The creation of singleton mode can be divided into lazy creation and hungry creation

    Lazy singleton mode

    Laziness literally means laziness. Because I'm lazy, I won't move if I can rest. I won't take the initiative if you don't let me work

    Therefore, the instance of lazy singleton mode is empty at first and will not be initialized until it is called

    There are many ways to implement lazy singleton mode. First, let's look at the first one

    Add the content in the red box and it becomes lazy singleton mode

    However, this singleton pattern may produce multiple instances in the case of concurrency

    The memory addresses of the instances obtained by the two threads are different, indicating that multiple instances are obtained

    This is because in the case of concurrency, thread A is suspended when it executes to A certain line and has not had time to create an instance. Like the following line

    At this time, thread B starts to execute. At line 18, it is judged that an instance has not been created, and thread B creates an instance

    Thread A is then invoked, and then executed down, and an instance is also created

    This problem is the same as the problem we encountered when we talked about the snowflake algorithm just now. It can be solved by synchronized

    With synchronized, when a thread executes code that is locked by synchronized, other threads can only wait.

    After the thread executes, a snowFlake instance is created. Then other threads can go in and execute

    When other threads go in and execute, they find that snowFlake is not null, so they will not create a new instance

    This solves the problem of lazy singleton mode creating multiple instances in the case of concurrency, but it is not perfect

    Imagine that when there is a large amount of concurrency, because only one thread can go in and execute, other threads can only wait outside.

    With more and more visits, more and more threads are blocked. When enough threads are blocked, it may lead to server downtime

    We can optimize this by adding a layer of non null judgment outside synchronized

    After adding the non null judgment of the outer layer, although synchronized will still block the subsequent threads

    However, after the first thread is executed, snowFlake is instantiated and is no longer null

    Because there is an outer layer of non empty judgment, subsequent threads will not go in and execute, nor will they be blocked, but will directly return

    This is a perfect lazy singleton mode

    Hungry Han single case mode

    Hungry Han style literally means hungry. Because I've been hungry, I've prepared delicious food for me in advance

    Therefore, the instance of hungry Chinese singleton mode is created in advance, that is, it is created when the class is loaded, rather than waiting until it is used

    Let's use the hungry man singleton pattern to optimize the snowflake algorithm we adapted before

    With the code in the red box, the snowflake algorithm becomes a hungry man type singleton mode.

    The snowFlake variable in the first line of the red box is modified by static. We all know that the static modified variable belongs to this class and is initialized and assigned when the class is loaded.

    This class will only be loaded once, so the snowFlake variable will only be initialized once, so as to ensure the singleton

    Source code

    The following is the source code of hungry and lazy snowflake algorithm singleton mode. Please take it yourself if you need it

    Implementation of snowflake algorithm in hungry Han single case mode

    package com.helianxiaowu.hungrySingleton;

    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;

    /**
     * @desc: Implementation of snowflake algorithm in hungry Han single case mode
     * @author: The official account: HL
     * @create: 2021-06-29 19:32
     **/
    public class SnowFlake {

        private static SnowFlake snowFlake = new SnowFlake();

        private SnowFlake() {}

        public static SnowFlake getInstance() {
            return snowFlake;
        }

        //Serial number. This parameter is used to control concurrency within the same millisecond
        private long sequence = 0L;
        //Time string of last generated number, format: yyMMddHHmmssSSS
        private String lastTime = "";

        public synchronized String getNum() {
            String nowTime = getTime(); //Get the current time string, format: yymmddhhmmssss
            String machineId = "01"; //Machine number. The machine number obtained here is 2. The actual project can be read from the configuration file
            //This time is not the same as the last time. The number is generated directly and returned
            if (!lastTime.equals(nowTime)) {
                sequence = 0L; //Reset serial number for next use
                lastTime = nowTime; //Update the time series to facilitate next use
                return new StringBuilder(nowTime).append(machineId).append(sequence).toString();
            }
            //This time and the last time are in the same millisecond, you need to use serial number to control concurrency
            if (sequence < 99) { //If the serial number does not reach the maximum value, the serial number is directly generated and returned
                sequence = sequence + 1;
                return new StringBuilder(nowTime).append(machineId).append(sequence).toString();
            }
            //The serial number reaches the maximum value and needs to wait for the next millisecond
            while (lastTime.equals(nowTime)) {
                nowTime = getTime();
            }
            sequence = 0L; //Reset serial number for next use
            lastTime = nowTime; //Update the time series to facilitate next use
            return new StringBuilder(nowTime).append(machineId).append(sequence).toString();
        }

        private String getTime() {
            return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyMMddHHmmssSSS"));
        }
    }

    Implementation of snowflake algorithm in lazy singleton mode

    package com.helianxiaowu.lazySingleton;

    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;

    /**
     * @desc: Implementation of snowflake algorithm in lazy singleton mode
     * @author: The official account: HL
     * @create: 2021-06-29 19:32
     **/
    public class SnowFlake {

        private static SnowFlake snowFlake = null;

        private SnowFlake() {}

        public static SnowFlake getInstance() {
            if (snowFlake == null) {
                synchronized (SnowFlake.class) {
                    if (snowFlake == null) {
                        snowFlake = new SnowFlake();
                    }
                    return snowFlake;
                }
            }
            return snowFlake;
        }

        //Serial number. This parameter is used to control concurrency within the same millisecond
        private long sequence = 0L;
        //Time string of last generated number, format: yyMMddHHmmssSSS
        private String lastTime = "";

        public synchronized String getNum() {
            String nowTime = getTime(); //Get the current time string, format: yymmddhhmmssss
            String machineId = "01"; //Machine number. The machine number obtained here is 2. The actual project can be read from the configuration file
            //This time is not the same as the last time. The number is generated directly and returned
            if (!lastTime.equals(nowTime)) {
                sequence = 0L; //Reset serial number for next use
                lastTime = nowTime; //Update the time series to facilitate the next use
                return new StringBuilder(nowTime).append(machineId).append(sequence).toString();
            }
            //This time and the last time are in the same millisecond, you need to use serial number to control concurrency
            if (sequence < 99) { //If the serial number does not reach the maximum value, the serial number is directly generated and returned
                sequence = sequence + 1;
                return new StringBuilder(nowTime).append(machineId).append(sequence).toString();
            }
            //The serial number reaches the maximum value and needs to wait for the next millisecond
            while (lastTime.equals(nowTime)) {
                nowTime = getTime();
            }
            sequence = 0L; //Reset serial number for next use
            lastTime = nowTime; //Update the time series to facilitate next use
            return new StringBuilder(nowTime).append(machineId).append(sequence).toString();
        }

        private String getTime() {
            return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyMMddHHmmssSSS"));
        }
    }

    Topics: Java Algorithm Distribution Singleton pattern