缩小版雪花算法的实现与多线程并发测试
我们设计数据表的时候,一般都会有ID字段,ID的生成方法有多种,比如数据库自增,UUID,雪花算法等。
考虑到以后数据的增长,分库分表,分布式等要求,我们选择雪花算法来生成ID。
开发Web系统需要后端跟前端交互,前端JavaScript支持的最大整型是53位,超过53位会丢失精度,原版的雪花算法会超过53位,我们使用缩小版的雪花算法,把位数减小到53位。
原版Snowflake算法的极限是每毫秒的每一个节点生成4059个id值,也就是说每毫秒的极限是生成023*4059=4 152 357个id值 ,缩小后极限是每秒生成15*131071=1 966 065个分布式id,够我们在开发里面的日常使用了。
package cn.gintone.asso.util; /** * @description:缩小版的雪花算法 * @author:Elon He * @create:2020-10-06 */ public class SnowflakeMini { /** * 开始时间截 (1970-01-01) */ private final static long twepoch = 0L; /** * 机器id,范围是1到15 */ private final static long workerId =1L; /** * 机器id所占的位数,占4位 */ private final static long workerIdBits = 4L; /** * 支持的最大机器id,结果是15 */ private final static long maxWorkerId = ~(-1L << workerIdBits); /** * 生成序列占的位数 */ private final static long sequenceBits = 15L; /** * 机器ID向左移15位 */ private final static long workerIdShift = sequenceBits; /** * 生成序列的掩码,这里为最大是32767 (1111111111111=32767) */ private final static long sequenceMask = ~(-1L << sequenceBits); /** * 时间截向左移19位(4+15) */ private final static long timestampLeftShift = 19L; /** * 秒内序列(0~32767) */ private static long sequence = 0L; /** * 上次生成ID的时间截 */ private static long lastTimestamp = -1L; /** * 获得下一个ID (该方法是线程安全的) * * @return SnowflakeId */ public static synchronized long nextId() { //返回以秒为单位的当前时间 long timestamp = timeGen(); //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 if (timestamp < lastTimestamp) { throw new RuntimeException( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //蓝色代码注释结束 //红色代码注释开始 //如果是同一时间生成的,则进行秒内序列 if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; //秒内序列溢出 if (sequence == 0) { //阻塞到下一个秒,获得新的秒值 timestamp = tilNextMillis(lastTimestamp); } //时间戳改变,秒内序列重置 } //红色代码注释结束 //绿色代码注释开始 else { sequence = 0L; } //绿色代码注释结束 //上次生成ID的时间截 lastTimestamp = timestamp; //黄色代码注释开始 //移位并通过或运算拼到一起组成53 位的ID return ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence; //黄色代码注释结束 } /** * 阻塞到下一个秒,直到获得新的时间戳 * * @param lastTimestamp 上次生成ID的时间截 * @return 当前时间戳 */ protected static long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回以秒为单位的当前时间 * * @return 当前时间(秒) */ protected static long timeGen() { return System.currentTimeMillis()/1000L; } }
创建两个线程,同时测试ID获取。
测试线程类A
package cn.gintone.asso; import cn.gintone.asso.util.SnowflakeMini; /** * @description:线程A * @author:Elon He * @create:2020-10-06 */ public class ThreadA extends Thread{ @Override public void run() { super.run(); for (int i = 0; i < 10; i++) { long id = SnowflakeMini.nextId(); System.out.println("A:"+id); } } }
测试线程类B
package cn.gintone.asso; import cn.gintone.asso.util.SnowflakeMini; /** * @description:线程B * @author:Elon He * @create:2020-10-06 */ public class ThreadB extends Thread{ @Override public void run() { super.run(); for (int i = 0; i < 10; i++) { long id = SnowflakeMini.nextId(); System.out.println("B:"+id); } } }
测试类(注释掉的代码是修改成静态方法前的测试方法)
package cn.gintone.asso; /** * @description:雪花算法测试类 * @author:Elon He * @create:2020-10-06 */ public class TestSnowflake { public static void main(String[] args) { //不同线程使用同一个对象,不重复 // SnowflakeMini idWorker = new SnowflakeMini(0); // ThreadA t1 = new ThreadA(idWorker); // ThreadB t2 = new ThreadB(idWorker); // t1.start(); // t2.start(); //不同线程使用不同对象,会重复 // SnowflakeMini idWorker1 = new SnowflakeMini(0); // SnowflakeMini idWorker2 = new SnowflakeMini(0); // ThreadA t1 = new ThreadA(idWorker1); // ThreadB t2 = new ThreadB(idWorker2); // t1.start(); // t2.start(); //nextId修改成静态方法后测试,不重复 ThreadA t1 = new ThreadA(); ThreadB t2 = new ThreadB(); t1.start(); t2.start(); } }
测试结果

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
代理模式
微信公众号:云计算通俗讲义 持续输出技术干货,欢迎关注! 通过本文你将了解: 背景 概述 模式结构 代码实现 特点 应用场景 背景 在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的动机。 概述 代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式。 模式结构 代理模式包含如下角色: Subject: 抽象主题角色 抽象主题角色声明了真实主题和代理主题的共同接口,实现了调用方和服务方的解耦。 Proxy: 代理主题角色 代理主题角色内部包含对真实主题的引用,从而可以在任何时候操作真实主题对象。 RealSubject: 真实主题角色...
- 下一篇
高级并发编程系列十三(一文搞懂原子类)
1.考考你 大家周末好!又到我们一周分享的时候了。相信作为资深程序员的你,对于AtomicInteger这样的类,即以Atomic开始的类一定不会感到陌生。我们在翻看很多框架源码、或者第三方组件都会经常看到它们,如影随形。 那么问题来了,像Atomicxxx这样的类,到底是什么意思呢?从字面意思比较好理解,Atomic即原子性,那么Atomicxxx即原子类。讲到这里,你一定还记得我们说过线程安全的三个基本要素,我们一起来回顾一下:可见性、原子性、有序性。原子类的原子性,讲的就是这个原子性,于是你可以先记住一个结论:原子类,它是线程安全的类。 到这里有朋友可能会提出质疑:你说线程安全,就线程安全吗?我不服,你没有讲清楚。我不听,我不听......好吧,看官们莫急,且听我一步一步分析,娓娓道来,话说...... #考考你: 1.你真的理解原子类的核心思想吗 2.你在你的项目中,有直接用到过原子类吗 2.案例 2.1.自增操作案例 2.1.1.普通变量版本 案例描述: 定义一个普通的int型变量value,初始值为:0 开启两个线程thread_1,thread_2并行执行value+...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS7设置SWAP分区,小内存服务器的救世主
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7安装Docker,走上虚拟化容器引擎之路
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS关闭SELinux安全模块