专栏首页Java技术栈厉害了,美女同事用单例模式实现了雪花算法!

厉害了,美女同事用单例模式实现了雪花算法!

雪花算法

雪花算法适用于生成全局唯一的编号,比如数据库主键id,订单编号等

至于为什么叫雪花算法,是因为科学家通过研究认为自然界中不存在两片完全相同的雪花,所以这种算法用雪花来命名也是强调它生成的编号不会重复吧

雪花算法生成的编号共有64bit,刚好是java中long的最大范围

雪花算法是用64位的二进制数字表示

在二进制中,第一位是符号位,表示正数或负数,正数是0,负数是1

因为生成唯一编号不需要负数,所以第一位永远是0,相当于没用

用41位表示时间戳,这个时间戳是当前时间和指定时间的毫秒差。

比如指定时间是2021-06-30 11:07:20,在1秒之后调用了雪花算法,那么这个毫秒差就是1000

41位的二进制,可以表示 「241-1」 个正数,这么多个毫秒差理论上是可以使用69年。

(241-1) / (1000∗60∗60∗24∗365)≈69.73

IT行业日益更新的当下,很少有程序能持续使用69年。所以,足够了

用10位表示机器编号,在分布式环境下最多支持 「210=1024」 个机器

用12位表示序列号,在同一个毫秒内,同一个机器上用序列号来控制并发。同一个毫秒内可以允许有 「212=4096」 个并发

总结下来就是,即使你的程序在分布式环境下有1024台负载,每个负载每毫秒的并发量是4096,雪花算法生成的唯一编号也不会重复,算法不可谓不强大

以上是基于二进制讲的雪花算法,比较晦涩难懂,也不利于接下来我们要讨论的内容

所以,我们对雪花算法做一点修改,改成如下方式

用15个字符表示时间串,比如2021年06月30日14点52分30秒226毫秒可以表示为210630145230226。这么表示可读性更强,而且百年之内不会重复

用两位数字表示机器编号,最多可以支持100个机器

用两位数字表示序列号,一毫秒内支持100个并发

接下来把我们改编后的算法用代码实现一下(这里贴的是图片,文末会附上源码)

这就实现了我们改编过后的雪花算法。但是,仔细想一下,代码还存在并发问题

在两个线程同时执行这块代码时获取的唯一编号有可能重复

这是因为线程A执行到某一行时被挂起,还没来得及修改lastTime的值。比如线程A执行到这一行时被挂起

这时线程B开始执行,判断lastTimenowTime还是equals的,线程B就会继续执行并且获得一个编号

然后线程A被唤起继续执行也获取到一个编号,这时两个线程获取到的编号就重复了

可以用java的synchronized关键字把并发改为同步

加上synchronized后,当线程A在方法中正执行时,线程B只能在方法外等待,不能进去执行,这就解决了上面说的并发问题

但是,还有另外一个问题。

我们都知道synchronized只针对同一个实例有效,当有多个实例时,多个实例之间无法控制

一旦产生多个实例时,多个实例之间产生的编号就有可能重复

所以我们不能让这个类的对象产生多个实例,只能让它始终保持只有一个实例

说到这里,我们首先想到的就是单例模式。另外,设计模式系列面试题和答案全部整理好了,微信搜索Java技术栈,在后台发送:面试,可以在线阅读。

单例模式最大的特点就是在任何情况下最多只有一个实例,所以这里使用单例模式来解决这个问题再合适不过

先说一下单例模式怎么保证单例,要想保证单例就不能让别人随便创建实例。

最好的办法就是把构造器私有化,让它是private的。私有化之后只有这个类自己能创建实例,其它的类都没有调用这个类的构造器的权限

这个类只创建一个实例,那么它就是单例的

单例模式的创建可分为懒汉式创建和饿汉式创建

懒汉式单例模式

懒汉式从字面意思理解就是懒嘛,因为我懒,能歇着就不会动,你没让我干活我就不会主动去干

所以,懒汉式单例模式的实例一开始为空,等到被调用时才会初始化

懒汉式单例模式有多种实现方式,首先我们先来看第一种

加上红框中的内容就变成了懒汉式单例模式

但是这个单例模式在并发情况下是有可能会产生多个实例的

两个线程获取的实例的内存地址是不一样的,说明获取到的是多个实例

这是因为在并发情况下线程A执行到某一行时被挂起,还没来得及创建实例。比如下面这一行

这时线程B开始执行,到18行时判断还没创建实例,线程B就创建了一个实例

然后线程A被唤起,接着往下执行,也会创建一个实例

这个问题和我们刚才讲雪花算法的时候遇到的问题一样,可以用synchronized来解决

加上synchronized以后,当一个线程在执行被synchronized锁住的代码时,其他线程只能等待。

当这个线程执行完之后,创建了snowFlake实例。然后别的线程才能进去执行

当别的线程进去执行的时候,发现snowFlake不是null了,就不会创建新的实例了

这就解决了懒汉式单例模式在并发情况下创建多个实例的问题,但是还不够完美

试想一下,当并发量很大的时候,因为只有一个线程可以进去执行,其他线程只能在外面等待。

随着访问量越来越大,被阻塞的线程也越来越多。当阻塞的线程足够多时,就有可能导致服务器宕机

我们可以这样优化,在synchronized外面再加一层非空判断

加上外层的非空判断之后,虽然synchronized还是会阻塞后面过来的线程

但是,当第一个线程执行完之后,snowFlake被实例化,不再为null

因为有外层的非空判断,所以后续的线程不会再进去执行,也不会被阻塞,而是直接return了

这就是一个完美的懒汉式单例模式了

饿汉式单例模式

饿汉式从字面意思理解就是饿嘛,因为我一直饿,所以把好吃的都提前给我准备好

所以饿汉式单例模式的实例是提前创建好的,也就是类加载的时候就创建了,而不是等到用的时候再创建

我们用饿汉式单例模式来优化一下我们之前改编的雪花算法

加上红框中的代码雪花算法就变成了饿汉式单例模式。

红框中第一行的snowFlake变量是被static修饰的,我们都知道static修饰的变量是属于这个类的,在类加载的时候就进行了初始化赋值。

而这个类只会被加载一次,所以snowFlake变量只会被初始化一次,从而保证了单例。

推荐:单例模式的 8 种写法,非常全!

源码

下面附上饿汉式和懒汉式创建雪花算法单例模式的源码,需要的请自取

「饿汉式」单例模式实现雪花算法

package com.helianxiaowu.hungrySingleton;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @desc: 饿汉式单例模式实现雪花算法
 * @author: 公众号:赫连小伍
 * @create: 2021-06-29 19:32
 **/
public class SnowFlake {

    private static SnowFlake snowFlake = new SnowFlake();

    private SnowFlake() {}

    public static SnowFlake getInstance() {
        return snowFlake;
    }

    // 序列号,同一毫秒内用此参数来控制并发
    private long sequence = 0L;
    // 上一次生成编号的时间串,格式:yyMMddHHmmssSSS
    private String lastTime = "";

    public synchronized String getNum() {
        String nowTime = getTime(); // 获取当前时间串,格式:yyMMddHHmmssSSS
        String machineId = "01"; // 机器编号,这里假装获取到的机器编号是2。实际项目中可从配置文件中读取
        // 本次和上次不是同一毫秒,直接生成编号返回
        if (!lastTime.equals(nowTime)) {
            sequence = 0L; // 重置序列号,方便下次使用
            lastTime = nowTime; // 更新时间串,方便下次使用
            return new StringBuilder(nowTime).append(machineId).append(sequence).toString();
        }
        // 本次和上次在同一个毫秒内,需要用序列号控制并发
        if (sequence < 99) { // 序列号没有达到最大值,直接生成编号返回
            sequence = sequence + 1;
            return new StringBuilder(nowTime).append(machineId).append(sequence).toString();
        }
        // 序列号达到最大值,需要等待下一毫秒的到来
        while (lastTime.equals(nowTime)) {
            nowTime = getTime();
        }
        sequence = 0L; // 重置序列号,方便下次使用
        lastTime = nowTime; // 更新时间串,方便下次使用
        return new StringBuilder(nowTime).append(machineId).append(sequence).toString();
    }

    private String getTime() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyMMddHHmmssSSS"));
    }
}

「懒汉式」单例模式实现雪花算法

package com.helianxiaowu.lazySingleton;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * @desc: 懒汉式单例模式实现雪花算法
 * @author: 公众号:赫连小伍
 * @create: 2021-06-29 19:32
 **/
public class SnowFlake {

    private static SnowFlake snowFlake = null;

    private SnowFlake() {}

    public static SnowFlake getInstance() {
        if (snowFlake == null) {
            synchronized (SnowFlake.class) {
                if (snowFlake == null) {
                    snowFlake = new SnowFlake();
                }
                return snowFlake;
            }
        }
        return snowFlake;
    }

    // 序列号,同一毫秒内用此参数来控制并发
    private long sequence = 0L;
    // 上一次生成编号的时间串,格式:yyMMddHHmmssSSS
    private String lastTime = "";

    public synchronized String getNum() {
        String nowTime = getTime(); // 获取当前时间串,格式:yyMMddHHmmssSSS
        String machineId = "01"; // 机器编号,这里假装获取到的机器编号是2。实际项目中可从配置文件中读取
        // 本次和上次不是同一毫秒,直接生成编号返回
        if (!lastTime.equals(nowTime)) {
            sequence = 0L; // 重置序列号,方便下次使用
            lastTime = nowTime; // 更新时间串,方便下次使用
            return new StringBuilder(nowTime).append(machineId).append(sequence).toString();
        }
        // 本次和上次在同一个毫秒内,需要用序列号控制并发
        if (sequence < 99) { // 序列号没有达到最大值,直接生成编号返回
            sequence = sequence + 1;
            return new StringBuilder(nowTime).append(machineId).append(sequence).toString();
        }
        // 序列号达到最大值,需要等待下一毫秒的到来
        while (lastTime.equals(nowTime)) {
            nowTime = getTime();
        }
        sequence = 0L; // 重置序列号,方便下次使用
        lastTime = nowTime; // 更新时间串,方便下次使用
        return new StringBuilder(nowTime).append(machineId).append(sequence).toString();
    }

    private String getTime() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyMMddHHmmssSSS"));
    }
}

本文分享自微信公众号 - Java技术栈(javastack)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-09-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 多服务器终端交互利器--polysh和atnodes到高逼格日志中心

    静儿
  • 如何用一句话通过面试?

    2018年11月,谷歌全球20多个办公室的员工,停下手上的工作,走上街头,举起标语罢工:me too。

    帅地
  • 一个前端妹子的悲欢编程之路

    王小婷
  • 女人新体验!大数据演算:打造你的专属内衣

      电视机面前的女性观众,碰过这样的烦恼吗?买内衣不想被店员贴身量胸围,或花了时间排队试穿,又买到不合身的内衣,但美国一家网路内衣公司True&Co,居然还推出...

    小莹莹
  • 程序员的江湖:从黑木崖到回龙观

    毕业时,带着一本《深入浅出MFC》和一张磨损严重的键盘去了北方的燕京,号称:一书一键闯天涯。

    康亮
  • 无人驾驶汽车首例在公共道路上的致死案,安全性质疑再起

    据外媒报道,美国当地时间3月19日,一辆Uber无人驾驶汽车在美国亚利桑那州坦佩发生交通事故,撞死一名正在过马路的49岁女性Elaine Herzberg。据了...

    机器人网
  • 假爱豆真AI,中国女子组合SNH48构建智能3D替身

    中国女孩偶像组合SNH48最新发布圣诞音乐视频,六位最受欢迎的明星一起唱歌和跳舞,值得注意的是,这六人都有假“替身”。

    新智元
  • 知乎上8个100K+高赞回答(筛选自63万个回答)

    最近知乎首页上老是看到这个话题:《知乎上的高票答案就是好的吗?》,很好奇目前高赞回答都有哪些?各有多少赞同数?于是继续这些天爬知乎数据的节奏,以大小V主页的回答...

    古柳_DesertsX
  • 别在看不起女程序媛了,一个高颜值女程序媛的日常

    今天这篇文,意义特殊,是我的一个迷妹程序媛-祈澈姑娘写的,她发给我后,我看了通篇,感觉写的很真实,而且又是记录女程序媛的日常,比较少见,所以我很有兴趣,相信大家...

    一个程序员的成长
  • 电视迷的好帮手:人工智能如何让你更爱看电视

    我们爱看电视。美国人有半数的休闲时光都用在电视上,而现在 Comcast 将使用人工智能让我们更爱上看电视这件事。 该公司使用 GPU 加速深度学习技术,让它更...

    GPUS Lady
  • Java基础语法(十一)类?对象?我能不能写个妹子类给自己造个(一个够吗?)对象?

    那么什么是类呢?类,分门别类的类,类别的类,人“类”就是我们现实生活中的一个类,而每一个人就是一个对象。 对象就是特殊个体,类就是一般个体,可能还是不那么好理...

    全栈程序员站长
  • 对话「福利姬」丨未成年人用户需要更多守护和引导

    “我是同学介绍入行的,感觉还挺好玩的。现在高中没时间,我一般中午和晚上帮小姐妹介绍客户卖点套图,每月还是能赚点零花钱。“

    腾讯举报中心
  • 身边有个漂亮女程序员是种什么样的体验?

    程序员,本来就被认为是奇葩的存在了,身上自带光(biao)环(qian),说的好听的就是人傻钱多好管理,还有另一种说法就是挣得多花的少死的早,在这里心疼程序猿们...

    企鹅号小编
  • 心理学|颜色是如何影响我们的情绪的?

    大数据文摘
  • 一朝入梦,终生不醒:再看红楼梦,也谈石头记

      红楼梦是我国四大名著之一,被众多学者冠以四大名著之首的美誉,也是我们从学生时代开始就被老师灌输需要阅读的经典书籍(虽然我们学生时代未必阅读或读完过)。古人曾...

    Edison Zhou
  • 什么是雪花模型

    版权声明:本文为王小雷原创文章,未经博主允许不得转载 https://blog.csdn.n...

    王小雷
  • 看看这份账单,月薪一万在北京只能过这样的日子...

    合租价三环平均一人都是3000+,四环也要2500+,在东西城和朝阳海淀等主要工作和教育地段还会更高,具体合租价格可以参考下图。

    51社保
  • 我也要成为跟你一样厉害的人 No.112

    无数次我们在平时闲得很的时候,都会想标题这么一个事情,就是我也要成为跟你一样厉害的人,在我眼里你就是很厉害很厉害很厉害。

    大蕉
  • 有网络的地方就有“三剑客”

    法国19世纪浪漫主义作家大仲马,在生前有一部代表作名为《三个火枪手》又名《三剑客》这部历史小说以法兰西国王路易十三朝代和权倾朝野的红衣主教黎塞留掌权这一时期的历...

    Panabit

扫码关注云+社区

领取腾讯云代金券