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"));
}
}