厉害了,美女同事用单例模式实现了雪花算法!(代码片段)

Java技术栈 Java技术栈     2022-12-29     440

关键词:

点击关注公众号,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技术栈看更多干货

获取 Spring Boot 实战笔记!

php用单例模式实现一个数据库类

使用单例模式的出发点:    1、php的应用主要在于数据库应用, 所以一个应用中会存在大量的数据库操作, 使用单例模式, 则可以避免大量的new 操作消耗的资源。  2、如果系统中需要有一个类来全局控制某... 查看详情

用java爬美女图片,这个厉害了。。(代码片段)

????????关注后回复 “进群” ,拉你进程序员交流群????????作者:Victor.Chang原文:blog.csdn.net/qq_35402412/article/details/113627625目的爬取搜狗图片上千张美女图片并下载到本地准备工作爬取地址:https://pic.sogou.com/pics?query... 查看详情

简单的单例模式(代码片段)

...不太合适,就需要一个方法方式来解决问题,这是最适合用单例模式了虽然我举的栗子可能不太合适, 查看详情

用java爬美女图片,厉害了。。(代码片段)

目的爬取搜狗图片上千张美女图片并下载到本地准备工作爬取地址:https://pic.sogou.com/pics?query=%E7%BE%8E%E5%A5%B3分析打开上面的地址,按F12开发者工具-NetWork-XHR-页面往下滑动XHR栏出现请求信息如下:RequestURL:https:/... 查看详情

laravel框架中运用单例模式(代码片段)

阅读目录解决问题场景单例模式控制器模型打印finalModel父类模型Model子类控制器打印解决问题场景发现在代码中不断的在newmodel()调用模型方法,这样子会不断的对模型进行实例化,并不断的销毁对象。比如:<?phpnamespaceApp\\Http\\... 查看详情

单例模式(代码片段)

...号。WEB中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。创建的一个对象需要消耗的资源过多,比如I/O与数据库的连接等。注意事项:getInstance()方法中需要使用同步锁synchronized(Singleton.class)防止多线程同时进... 查看详情

springbean什么时候用单例模式什么时候用多例?(代码片段)

SpringBean单例与多例在Spring中,bean可以被定义为两种模式:prototype(多例)和singleton(单例)单例多例说明singleton(单例)只有一个共享的实例存在,所有对这个bean的请求都会返回这个唯一的实... 查看详情

新来了个同事,设计模式用的是真优雅呀!代码如诗!!

之前有小伙伴私信我说看源码的时候感觉源码很难,不知道该怎么看,其实这有部分原因是因为没有弄懂一些源码实现的套路,也就是设计模式,所以本文我就总结了9种在源码中非常常见的设计模式,并列举... 查看详情

新来了个同事,设计模式用的是真优雅呀!代码如诗!!

之前有小伙伴私信我说看源码的时候感觉源码很难,不知道该怎么看,其实这有部分原因是因为没有弄懂一些源码实现的套路,也就是设计模式,所以本文我就总结了9种在源码中非常常见的设计模式,并列举... 查看详情

面向对象之单例模式(代码片段)

目录1.单例模式1.1什么是单例模式1.2为什么用单例模式1.3单例模式三种方式1.3.1使用类内部绑定方法特性,定义静态方法1.3.2利用装饰器实现单例模式1.3.3利用元类实现单例模式1.单例模式1.1什么是单例模式单例模式就是基于某种... 查看详情

设计模式--单例模式(代码片段)

...模式。我们这节就全面的来讲解一下单例模式。为什么要用单例模式单例模式理解起来非常简单。在一个系统中,一个类只允许创建一个对象,那这个类就是单例类,这种设计模式就叫做单例设计模式。为什么需要单例模式呢?... 查看详情

ios开发之利用单例模式完成两个viewcontroller之间的传值(代码片段)

单例文件内容如下.h@interfaceSingle:NSObject@property(nonatomic,copy)NSURL*value;+(instancetype)sharedInstance;@end.m@implementationSingle+(instancetype)sharedInstancestaticSingle*myInstance=nil;if(myInstance 查看详情

设计模式单例模式:4-静态代码块模式

...块中。  由于初始化的时间比饿汉模式更加早,我有个同事把它称之为:难民模式。 1publicfinalclassStaticBlock 查看详情

双重校验锁实现单例模式(代码片段)

...时无需加锁*该代码存在效率问题,因为即使已经产生了单例之后,之后调用了getInstance()方法之后仍然还会加锁解锁,*加锁和解锁会影响系统的性能*====================>*于是有了双重校验锁机制**/publicfinal 查看详情

python单例模式的实现方式(代码片段)

一.单例类单例模式(SingletonPattern)是Python中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象... 查看详情

nopcommerce源码架构学习-二单例模式实现代码分析

单例模式是是常用经典十几种设计模式中最简单的。.NET中单例模式的实现也有很多种方式。下面我来介绍一下NopCommerce中单例模式实现。我之前的文章就分析了一下nop中EngineContext的实现。EngineContext是把一个Web请求用Nop的EngineCont... 查看详情

单例模式(代码片段)

...列号WEB中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来创建的一个对象需要消耗的资源过多,比如I/O与数据库的连接等。注意get 查看详情

单例模式

运行期间有且仅有一个实例。(比较耗系统性能的时候用单例模式)关键点:1,一个类只有一个实例,只提供私有的构造器2,它必须自行创建这个实例,定义了静态的该类的私有对象3,它必须自行向整个系统提供这个实例,提... 查看详情