java 根据时间戳生成有序ID

瞧一瞧

  • 引言
  • 工具类
  • 测试
  • 结论

引言

我们常用的主键有这么几种
1. 数据库自增主键,比如mysql的autoincrement,这种插入快,但是识别度不高
2. uuid 这个号称是全球唯一的,但是无序,没有实际意义,只能保证唯一
3. 时间戳,这种在分布式的场景下就需要考虑更多种情况
4. 雪花算法 snow flake ,分布式全局唯一主键,很牛逼,但是我觉得用起来也挺麻烦哈哈哈

所以在并发情况没那么大的时候用一个工具类搞定,我就是这么懒

工具类

@Slf4j
public class NumUtil {

    private static long tmpID = 0;
    private static final long LOCK_TIME = 1;
    private static final long INCREASE_STEP = 1;
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmssSSS");
    private static final Lock LOCK = new ReentrantLock();


    public static long nextPkId() throws InterruptedException {
        //当前:(年、月、日、时、分、秒、毫秒)
        long timeCount;
        if (LOCK.tryLock(LOCK_TIME, TimeUnit.SECONDS)) {
            timeCount = Long.parseLong(sdf.format(new Date()));
            try {
                if (tmpID < timeCount) {
                    tmpID = timeCount;
                } else {
                    tmpID += INCREASE_STEP;
                    timeCount = tmpID;
                }
                return timeCount;
            } finally {
                LOCK.unlock();
            }
        } else {
            log.error("lock failed");
            return nextPkId();

        }
    }
}

贴上代码,这里用了当前时间,精确到毫秒级,如果有需要的话可以在实例化timeCount的时候乘以10或者100 1000之类的,这个看大家,然后加上锁,防止线程不安全的情况,加锁失败的时候递归,再来一次。也可以使用synchronized做成同步方法,当中的区别下次再讨论。
有评论说宕机会导致tmpID归0导致已经使用过超出当前时间的ID,所以持久化这个tmpID也是可以的。但这也就是在并发没那么高的情况下才使用这种方法,一般并发场景下还是分布式锁+推特的雪花算法解决。

测试

public static void numTest() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        int n = 10000;
        List<Long> list = new ArrayList<>();
        CountDownLatch latch = new CountDownLatch(n);
        for (int i = 0; i < n; i++) {
            executorService.execute(() -> {
                //执行业务请求
                try {
                    list.add(NumUtil.nextPkId());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
            });
        }
        try {
            // 一定记得加上timeout时间,防止阻塞主线程
            latch.await(3000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            log.error(e.getMessage());
        }
        //4.等待所有子任务完成,组装内容
        while (list.size() < n) {
            log.info("集合长度 >>> {}",list.size());
        }
        //5.关闭线程池
        executorService.shutdown();

        for (Long aLong : list) {
            System.out.println(aLong);
        }
    }

然后噼里啪啦打印了一万个ID,没有重复的,一秒以内生成
java 根据时间戳生成有序ID_第1张图片

结论

当然这种只是为了单体或者是并发没有高到那么离谱的场景下使用,效率我觉得还不错,分布式的场景下可能需要用到redis的自增来计数以达到数据安全的效果,有什么问题的话大家可以提出来讨论

你可能感兴趣的:(java)