工具类封装实战(五)线程安全的雪花算法

举报
小鲍侃java 发表于 2021/09/09 22:43:45 2021/09/09
3k+ 0 0
【摘要】 雪花算法实现 package com.yjd.comm.util;/** * Created by pc on 2017/8/16 0016. */ /** * Twitter_Snowflake<br> * SnowFlake的结构如下(每部分用-分开):<br> * 0 - 0000000000 00000...

雪花算法实现


      package com.yjd.comm.util;/**
       * Created by pc on 2017/8/16 0016.
       */
      /**
       * Twitter_Snowflake<br>
       * SnowFlake的结构如下(每部分用-分开):<br>
       * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
       * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
       * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
       * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
       * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
       * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
       * 加起来刚好64位,为一个Long型。<br>
       * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
       */
      public class SnowflakeIdWorker {
         // ==============================Fields===========================================
         /**
       * 开始时间截 (2015-01-01)
       */
         private final long twepoch = 1420041600000L;
         /**
       * 机器id所占的位数
       */
         private final long workerIdBits = 5L;
         /**
       * 数据标识id所占的位数
       */
         private final long datacenterIdBits = 5L;
         /**
       * 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
       */
         private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
         /**
       * 支持的最大数据标识id,结果是31
       */
         private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
         /**
       * 序列在id中占的位数
       */
         private final long sequenceBits = 12L;
         /**
       * 机器ID向左移12位
       */
         private final long workerIdShift = sequenceBits;
         /**
       * 数据标识id向左移17位(12+5)
       */
         private final long datacenterIdShift = sequenceBits + workerIdBits;
         /**
       * 时间截向左移22位(5+5+12)
       */
         private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
         /**
       * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
       */
         private final long sequenceMask = -1L ^ (-1L << sequenceBits);
         /**
       * 工作机器ID(0~31)
       */
         private long workerId;
         /**
       * 数据中心ID(0~31)
       */
         private long datacenterId;
         /**
       * 毫秒内序列(0~4095)
       */
         private long sequence = 0L;
         /**
       * 上次生成ID的时间截
       */
         private long lastTimestamp = -1L;
         //==============================Constructors=====================================
         /**
       * 构造函数
       *
       * @param workerId 工作ID (0~31)
       * @param datacenterId 数据中心ID (0~31)
       */
         public SnowflakeIdWorker(long workerId, long datacenterId) {
             if (workerId > maxWorkerId || workerId < 0) {
                 throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
              }
             if (datacenterId > maxDatacenterId || datacenterId < 0) {
                 throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
              }
             this.workerId = workerId;
             this.datacenterId = datacenterId;
          }
         // ==============================Methods==========================================
         /**
       * 获得下一个ID (该方法是线程安全的)
       *
       * @return SnowflakeId
       */
         public 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;
             //移位并通过或运算拼到一起组成64位的ID
             return ((timestamp - twepoch) << timestampLeftShift) //
                      | (datacenterId << datacenterIdShift) //
                      | (workerId << workerIdShift) //
                      | sequence;
          }
         /**
       * 阻塞到下一个毫秒,直到获得新的时间戳
       *
       * @param lastTimestamp 上次生成ID的时间截
       * @return 当前时间戳
       */
         protected long tilNextMillis(long lastTimestamp) {
             long timestamp = timeGen();
             while (timestamp <= lastTimestamp) {
                  timestamp = timeGen();
              }
             return timestamp;
          }
         /**
       * 返回以毫秒为单位的当前时间
       *
       * @return 当前时间(毫秒)
       */
         protected long timeGen() {
             return System.currentTimeMillis();
          }
         //==============================Test=============================================
         /**
       * 测试
       */
         public static void main(String[] args) {
              SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 1);
             long startime = System.currentTimeMillis();
             for (int i = 0; i < 4000000; i++) {
                 long id = idWorker.nextId();
      // System.out.println(Long.toBinaryString(id));
      // System.out.println(id);
              }
              System.out.println(System.currentTimeMillis() - startime);
          }
      }
  
 

解决线程安全问题


      @Component
      public class SnowflakeComponent {
         @Value("${server.datacenterId}")
         private long datacenterId;
         @Value("${server.workId}")
         private long workId;
         private static volatile SnowflakeIdWorker instance;
         public SnowflakeIdWorker getInstance() {
             if (instance == null) {
                 synchronized (SnowflakeIdWorker.class) {
                     if (instance == null) {
                          instance = new SnowflakeIdWorker(workId, datacenterId);
                      }
                  }
              }
             return instance;
          }
      }
  
 

application.yml


      server:
       port: 8001
       workId: 0
       datacenterId: 0 #雪花算法的数据中心id,在java 启动命令中定义,四台为0123
  
 

调用生成唯一主键


         @Autowired
          private SnowflakeComponent snowflakeComponent;
         snowflakeComponent.getInstance().nextId()
  
 

文章来源: baocl.blog.csdn.net,作者:小黄鸡1992,版权归原作者所有,如需转载,请联系作者。

原文链接:baocl.blog.csdn.net/article/details/110871632

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。