关键词:
点击关注公众号,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开始执行,判断lastTime
和nowTime
还是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
变量只会被初始化一次,从而保证了单例。
源码
下面附上饿汉式和懒汉式创建雪花算法单例模式的源码,需要的请自取
「饿汉式」单例模式实现雪花算法
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,它必须自行向整个系统提供这个实例,提... 查看详情