我的编程空间,编程开发者的网络收藏夹
学习永远不晚

java算法之静态内部类实现雪花算法

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

java算法之静态内部类实现雪花算法

概述

在生成表主键ID时,我们可以考虑主键自增 或者 UUID,但它们都有很明显的缺点

主键自增:1、自增ID容易被爬虫遍历数据。2、分表分库会有ID冲突。

UUID: 1、太长,并且有索引碎片,索引多占用空间的问题 2、无序。

雪花算法就很适合在分布式场景下生成唯一ID,它既可以保证唯一又可以排序。为了提高生产雪花ID的效率,

在这里面数据的运算都采用的是位运算

一、概念

1、原理

SnowFlake算法生成ID的结果是一个64bit大小的整数,它的结构如下图:

算法描述:

1bit 因为二进制中最高位是符号位,1表示负数,0表示正数。生成的ID都是正整数,所以最高位固定为0。

41bit-时间戳 精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。

10bit-工作机器id 10位的机器标识,10位的长度最多支持部署1024个节点。

12bit-序列号 序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号。
12位(bit)可以表示的最大正整数是,即可以用0、1、2、3、....4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。

说明 由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。

二、静态类部类单例模式生产雪花ID代码

下面生成雪花ID的代码可以用于线上分布式项目中来生成分布式主键ID,因为设计采用的静态内部类的单例模式,通过加synchronized锁来保证在

同一个服务器线程安全。至于不同服务器其实是不相关的,因为它们的机器码是不一致的,所以就算同一时刻两台服务器都产生了雪花ID,那也不会一样的。

1、代码


public class SnowIdUtils {
    
    private static class SnowFlake {

        
        private static final SnowIdUtils.SnowFlake SNOW_FLAKE = new SnowIdUtils.SnowFlake();
        
        private final long START_TIMESTAMP = 1557489395327L;
        
        private final long SEQUENCE_BIT = 12;
        
        private final long MACHINE_BIT = 10;
        
        private final long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT;
        
        private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
        
        private final long MAX_MACHINE_ID = ~(-1L << MACHINE_BIT);
        
        private long machineIdPart;
        
        private long sequence = 0L;
        
        private long lastStamp = -1L;

        
        private SnowFlake() {
            //模拟这里获得本机机器编码
            long localIp = 4321;
            //localIp & MAX_MACHINE_ID最大不会超过1023,在左位移12位
            machineIdPart = (localIp & MAX_MACHINE_ID) << SEQUENCE_BIT;
        }
        
        public synchronized long nextId() {
            long currentStamp = timeGen();
            //避免机器时钟回拨
            while (currentStamp < lastStamp) {
                // //服务器时钟被调整了,ID生成器停止服务.
                throw new RuntimeException(String.format("时钟已经回拨.  Refusing to generate id for %d milliseconds", lastStamp - currentStamp));
            }
            if (currentStamp == lastStamp) {
                // 每次+1
                sequence = (sequence + 1) & MAX_SEQUENCE;
                // 毫秒内序列溢出
                if (sequence == 0) {
                    // 阻塞到下一个毫秒,获得新的时间戳
                    currentStamp = getNextMill();
                }
            } else {
                //不同毫秒内,序列号置0
                sequence = 0L;
            }
            lastStamp = currentStamp;
            //时间戳部分+机器标识部分+序列号部分
            return (currentStamp - START_TIMESTAMP) << TIMESTAMP_LEFT | machineIdPart | sequence;
        }
        
        private long getNextMill() {
            long mill = timeGen();
            //
            while (mill <= lastStamp) {
                mill = timeGen();
            }
            return mill;
        }
        
        protected long timeGen() {
            return System.currentTimeMillis();
        }
    }

    
    public static long uniqueLong() {
        return SnowIdUtils.SnowFlake.SNOW_FLAKE.nextId();
    }
    
    public static String uniqueLongHex() {
        return String.format("%016x", uniqueLong());
    }

    
    public static void main(String[] args) throws InterruptedException {
        //计时开始时间
        long start = System.currentTimeMillis();
        //让100个线程同时进行
        final CountDownLatch latch = new CountDownLatch(100);
        //判断生成的20万条记录是否有重复记录
        final Map<Long, Integer> map = new ConcurrentHashMap();
        for (int i = 0; i < 100; i++) {
            //创建100个线程
            new Thread(() -> {
                for (int s = 0; s < 2000; s++) {
                    long snowID = SnowIdUtils.uniqueLong();
                    log.info("生成雪花ID={}",snowID);
                    Integer put = map.put(snowID, 1);
                    if (put != null) {
                        throw new RuntimeException("主键重复");
                    }
                }
                latch.countDown();
            }).start();
        }
        //让上面100个线程执行结束后,在走下面输出信息
        latch.await();
        log.info("生成20万条雪花ID总用时={}", System.currentTimeMillis() - start);
    }
}

2、测试结果

从图中我们可以得出

1、在100个线程并发下,生成20万条雪花ID的时间大概在1.6秒左右,所有所性能还是蛮ok的。

2、生成20万条雪花ID并没有一条相同的ID,因为有一条就会抛出异常了。

3、为什么说41位时间戳最长只能有69年

我们思考41的二进制,最大值也就41位都是1,也就是也就是说41位可以表示个毫秒的值,转化成单位年则是

我们可以通过代码泡一下就知道了。


public static void main(String[] args) {
    //41位二进制最小值
    String minTimeStampStr = "00000000000000000000000000000000000000000";
    //41位二进制最大值
    String maxTimeStampStr = "11111111111111111111111111111111111111111";
    //转10进制
    long minTimeStamp = new BigInteger(minTimeStampStr, 2).longValue();
    long maxTimeStamp = new BigInteger(maxTimeStampStr, 2).longValue();
    //一年总共多少毫秒
    long oneYearMills = 1L * 1000 * 60 * 60 * 24 * 365;
    //算出最大可以多少年
    System.out.println((maxTimeStamp - minTimeStamp) / oneYearMills);
}

运行结果

所以说雪花算法生成的ID,只能保证69年内不会重复,如果超过69年的话,那就考虑换个服务器部署吧,并且要保证该服务器的ID和之前都没有重复过。

以上就是java算法之静态内部类实现雪花算法的详细内容,更多关于java算法的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

java算法之静态内部类实现雪花算法

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Java如何实现雪花算法的原理

这篇文章主要介绍了Java如何实现雪花算法的原理,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。java基本数据类型有哪些Java的基本数据类型分为:1、整数类型,用来表示整数
2023-06-14

Java实现雪花算法的代码怎么写

这篇文章主要介绍了Java实现雪花算法的代码怎么写的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java实现雪花算法的代码怎么写文章都会有所收获,下面我们一起来看看吧。一、介绍SnowFlow算法是Twitte
2023-06-29

Java面向对象之如何实现静态内部类

小编给大家分享一下Java面向对象之如何实现静态内部类,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!/**知识点: 内部类1. 内部类的定义2. 内部类的优缺点3
2023-06-02

Java算法之BFS,DFS,动态规划和贪心算法的实现

广度优先搜索(BFS)和深度优先搜索(DFS)是图遍历算法中最常见的两种算法,主要用于解决搜索和遍历问题。动态规划和贪心算法则用来解决优化问题。本文就来看看这些算法的具体实现吧
2023-05-14

Java算法之BFS,DFS,动态规划和贪心算法如何实现

本篇内容主要讲解“Java算法之BFS,DFS,动态规划和贪心算法如何实现”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java算法之BFS,DFS,动态规划和贪心算法如何实现”吧!广度优先搜索
2023-07-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录