snowFlakeId v2
这次也做了测试,没问题放心用ヾ(≧▽≦*)o
大概思路是维护一个Map,里面放了生成时间以及一个AtomicLong,需要生成时进入Map查找当前时间节点对应的AtomicLong,addAndGet确保一个时间戳内的机器生成码不重复,全程都使用自旋,无任何锁,避免了上锁带来的上下文切换资源消耗.
使用时记得修改获取机器码的方法
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class SnowFlakeUtils {
//比特位移位数
private final static int MACHINE_SHIFT = 10;
private final static int TIME_SHIFT = MACHINE_SHIFT + 12;
//每秒最多生成
private static final int MAX_SEQUENCE = 4095;
//最大历史生成时间保存
private static final int MAX_HISTORY_TIME = 5;
//TODO 动态机器id
private static final long MACHINE_ID = machineId();
private static Long machineId() {
return 1024L;
}
//时间与生成码的对应表
private static volatile LinkedHashMap<Long, AtomicLong> timeSet;
//标记最早的时间
private static Long earliestGenerateTime = timeForNow();
//放置同时修改不安全的LinkedHashMap
private static final AtomicInteger mapBusy = new AtomicInteger(0);
//时间回调自旋次数以及单次睡眠时间
private final static int MAX_ROLL_BACK_TRY_TIME = 5;
private final static int ROLL_BACK_SLEEP_TIME = 10;
//生成码超出范围自旋次数以及单次睡眠时间
private static final int SEQUENCE_MAX_SPIN = 1000;
private static final long SEQUENCE_MAX_SLEEP = 10;
/**
* 生成一个snow flake id
*
* @return snow flake id
*/
public static long createId() {
//获取最新时间
AtomicLong sequence;
long id;
int spin = 0;
long lastUpdateTime = timeForNow();
for (; ; ) {
//没有合集,创建
if (timeSet == null) {
if (mapBusy.get() == 0) {
createTimeSet();
}
} else if (timeSet.get(lastUpdateTime) == null && mapBusy.get() == 0) {
//没有这个时间戳,创建
createTimeStamp(lastUpdateTime);
} else if ((sequence = timeSet.get(lastUpdateTime)) != null) {
//生成码未满,返回
if ((id = sequence.addAndGet(1)) < MAX_SEQUENCE) {
return (lastUpdateTime << TIME_SHIFT | MACHINE_ID << MACHINE_SHIFT | id);
} else {
//生成码满,等待
if (spin++ >= SEQUENCE_MAX_SPIN) {
try {
Thread.sleep(SEQUENCE_MAX_SLEEP);
} catch (InterruptedException ignored) {
}
lastUpdateTime = timeForNow();
}
}
}
}
}
/**
* 生成这个时间戳的对应AtomicLong sequence
*
* @param currentTime 当前时间
*/
private static void createTimeStamp(Long currentTime) {
int rollBackTryTime = 0;
for (; ; ) {
if (timeSet == null) {
//timeSet懒加载,加载
createTimeSet();
} else if (currentTime < (earliestGenerateTime != null ? earliestGenerateTime : 0)) {
//时间回调,自旋尝试
try {
Thread.sleep(ROLL_BACK_SLEEP_TIME);
} catch (InterruptedException ignored) {
}
currentTime = timeForNow();
//尝试次数过多,抛出异常
if (rollBackTryTime++ >= MAX_ROLL_BACK_TRY_TIME) {
throw new RuntimeException("time roll back");
}
} else if (timeSet.get(currentTime) == null && mapBusy.compareAndSet(0, 1)) {
//生成当前时间cell
try {
if (timeSet.get(currentTime) == null) {
timeSet.putLast(currentTime, new AtomicLong(4094 - MAX_SEQUENCE));
final long[] smallestTime = new long[1];
timeSet.forEach((time, sequence) -> {
smallestTime[0] = (smallestTime[0] == 0) ? time : smallestTime[0];
smallestTime[0] = (smallestTime[0] < time) ? smallestTime[0] : time;
});
earliestGenerateTime = smallestTime[0];
//移除最早时间
if (timeSet.size() > MAX_HISTORY_TIME) {
timeSet.pollFirstEntry();
}
}
} finally {
mapBusy.set(0);
}
return;
} else if (timeSet.get(currentTime) != null) {
return;
}
}
}
/**
* 创建时间合集
*/
private static void createTimeSet() {
initCheck();
for (; ; ) {
//上锁保护安全
if (mapBusy.get() == 0 && timeSet == null && mapBusy.compareAndSet(0, 1)) {
if (timeSet == null) {
try {
timeSet = new LinkedHashMap<>();
timeSet.putLast(0L, null);
} finally {
mapBusy.set(0);
}
}
} else if (timeSet != null) {
return;
}
}
}
/**
* 获取最新时间
* 方便同步时间(复写,自定义时间
*
* @return 当前时间
*/
private static Long timeForNow() {
return System.currentTimeMillis();
}
/**
* 基本数值检查
*/
private static void initCheck() {
if (MACHINE_ID > 1024) throw new RuntimeException("machine id out of max, max 1023");
if (MAX_SEQUENCE > 4095) throw new RuntimeException("sequence out of max, max 4095");
}
}