雪花算法是用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();
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
做任何事情都要用心,要非常关注细节。看起来不起眼的、繁琐的工作做透了会有意想不到的价值。
当然要想成为一个技术大牛也需要一定的思想格局,思想决定未来你要往哪个方向去走, 建议多看一些人生规划方面的书籍,多学习名人的思想格局,未来你的路会走的更远。
更多的技术点思维导图我已经做了一个整理,涵盖了当下互联网最流行99%的技术点,在这里我将这份导图分享出来,以及为金九银十准备的一整套面试体系,上到集合,下到分布式微服务
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
大牛也需要一定的思想格局,思想决定未来你要往哪个方向去走, 建议多看一些人生规划方面的书籍,多学习名人的思想格局,未来你的路会走的更远。
更多的技术点思维导图我已经做了一个整理,涵盖了当下互联网最流行99%的技术点,在这里我将这份导图分享出来,以及为金九银十准备的一整套面试体系,上到集合,下到分布式微服务
[外链图片转存中…(img-o7sfn4D3-1713556953036)]
[外链图片转存中…(img-w3OxLtdb-1713556953036)]
[外链图片转存中…(img-DhIBYNvB-1713556953036)]
[外链图片转存中…(img-OMltqRKd-1713556953036)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!