java,面对单据的流水号,避免重复和并发问题
先讲一下思路
把一批单号生成,放到内存(你要连续,就放缓存),然后通过加锁的方式,去控制并发和重复的问题,当只剩下几个单号的时候,再重新生成一批
OK,上代码
NO.1 首先得有个配置,定义每次生成多少个单据号出来(放缓存里,不够了再生成)
// 配置项属性:
// 列名: val(当前值) , interval (增长区间), n(倍数)
// 其中 val = val + interval * n
//
// 开始时 val = 1 , interval = 10 , n = 2 ==> [1 , 20] 把这个区间信息记录下来,当取到 20这个值时,就得更新配置信息,更新为如下:
// val = 21 , interval = 10 , n = 2 ==> [21 , 40] 此时,区间信息增加了,同样也是存起来.....以此类推....一直累加,一直获取
/**对应配置信息的封装类*/
public class SysConf {
//当前值
private long val;
//增长区间
private int interval;
//倍数
private int n;
public long getVal() {
return val;
}
public void setVal(long val) {
this.val = val;
}
public int getInterval() {
return interval;
}
public void setInterval(int interval) {
this.interval = interval;
}
public int getN() {
return n;
}
public void setN(int n) {
this.n = n;
}
}
NO.2 假设这个配置项是存在数据库(或缓存中) — 这里就模拟一下
/**对应的操作类, -- 即对配置信息进行查询和更新*/
public class DbData {
static SysConf sysConf;
public static SysConf query(){
sysConf = new SysConf();
//初始单据号为1
sysConf.setVal(1);
//增长区间设为5 (一般是固定值)
sysConf.setInterval(5);
//增长倍数设置为2 (一般是固定值)
sysConf.setN(2);
return sysConf;
}
public static SysConf update(){
//更新的时候,更新val就行了,因为 interval 和 n 一般都是固定值
long val = sysConf.getVal() + sysConf.getInterval() * sysConf.getN();
sysConf.setVal(val);
return sysConf;
}
}
NO.3 真正生成单据号的类
public class TicketNoTool {
//边界值(超过这个值,就得重新更新数据库)
static long limitVal;
//下一个号码值
static long nextVal;
//增长值
static int n;
//信号量,只有一个许可证
static Semaphore semaphore = new Semaphore(1);
//是否初始化过配置信息
static boolean isInit = false;
/**初始化方法
*/
public static void init (){
if(isInit){
return;
}
//查库,获取配置
SysConf sysConf = DbData.query();
//初始化单据号配置信息 , 计算出区间值,即 [nextVal , limitVal]
nextVal = sysConf.getVal();
n = sysConf.getN();
limitVal = sysConf.getVal() + sysConf.getInterval() * sysConf.getN();
isInit = true;
}
//获取单据号
public static long getTicketNo(){
try {
//获取许可证
semaphore.acquire();
long result = nextVal;
//每次加1
nextVal += 1;
//如果值超过了区间的上限,则需要进行更新(即重新计算区间范围)
if(nextVal >= limitVal){
update();
}
return result;
} catch (InterruptedException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
//释放
semaphore.release();
}
}
//更新数据库中配置信息
private static void update(){
//no.1 更新数据库中配置
SysConf newSysConf = DbData.update();
//no.2 更新完后,更改 limitVal , nextVal , n 的值
nextVal = newSysConf.getVal();
n = newSysConf.getN();
limitVal = newSysConf.getVal() + newSysConf.getInterval() * newSysConf.getN();
}
到这里就进行main方法测试
public static void main(String[] args) {
//初始化
TicketNoTool.init();
//定义3个线程来获取单据号
for(int i = 0 ; i < 3 ; i ++ ) {
final int j = i;
new Thread(() -> {
//每个线程拿22个单据号
int num = 0;
while (num++ < 22) {
long orderNo = TicketNoTool.getTicketNo();
System.out.println("Thread--"+j+" getTicketNo = " + orderNo);
}
}).start();
}
}
展示运行结果(部分)
最后贴上全部代码(手残党福利,可直接进行复制),这个demo只是基于内存,单jvm的,如果是集群的,就不一样处理了,就得用分布式锁了
广告与公告:这是群主carlo所写,经过同意,我照搬过来的,感谢群主技术支持,本群Q## 号(212603891),欢迎有志程序猿,程序媛加入
package com.tzm; /**
* @version 1.0
* @class: TicketNoTool
* @author: carlo
* @mail: cwh@henghaodata.com
* @date: 2020/8/5 17:04
* @description: 单据号生成器
*
*
* 高并发情况下获取连续单据号,效率(一批一批的生成) + 不重复(上锁)
*
*/
import java.util.concurrent.Semaphore;
//NO.1 首先得有个配置,定义每次生成多少个单据号出来(放缓存里,不够了再生成)
//
// 配置项属性:
// 列名: val(当前值) , interval (增长区间), n(倍数)
// 其中 val = val + interval * n
//
// 开始时 val = 1 , interval = 10 , n = 2 ==> [1 , 20] 把这个区间信息记录下来,当取到 20这个值时,就得更新配置信息,更新为如下:
// val = 21 , interval = 10 , n = 2 ==> [21 , 40] 此时,区间信息增加了,同样也是存起来.....以此类推....一直累加,一直获取
/**对应配置信息的封装类*/
class SysConf {
//当前值
private long val;
//增长区间
private int interval;
//倍数
private int n;
public long getVal() {
return val;
}
public void setVal(long val) {
this.val = val;
}
public int getInterval() {
return interval;
}
public void setInterval(int interval) {
this.interval = interval;
}
public int getN() {
return n;
}
public void setN(int n) {
this.n = n;
}
}
//NO.2 假设这个配置项是存在数据库(或缓存中) --- 这里就模拟一下
/**对应的操作类, -- 即对配置信息进行查询和更新*/
class DbData {
static SysConf sysConf;
public static SysConf query(){
sysConf = new SysConf();
//初始单据号为1
sysConf.setVal(1);
//增长区间设为5 (一般是固定值)
sysConf.setInterval(5);
//增长倍数设置为2 (一般是固定值)
sysConf.setN(2);
return sysConf;
}
public static SysConf update(){
//更新的时候,更新val就行了,因为 interval 和 n 一般都是固定值
long val = sysConf.getVal() + sysConf.getInterval() * sysConf.getN();
sysConf.setVal(val);
return sysConf;
}
}
//NO.3 真正生成单据号的类
public class TicketNoTool {
//边界值(超过这个值,就得重新更新数据库)
static long limitVal;
//下一个号码值
static long nextVal;
//增长值
static int n;
//信号量,只有一个许可证
static Semaphore semaphore = new Semaphore(1);
//是否初始化过配置信息
static boolean isInit = false;
/**初始化方法
*/
public static void init (){
if(isInit){
return;
}
//查库,获取配置
SysConf sysConf = DbData.query();
//初始化单据号配置信息 , 计算出区间值,即 [nextVal , limitVal]
nextVal = sysConf.getVal();
n = sysConf.getN();
limitVal = sysConf.getVal() + sysConf.getInterval() * sysConf.getN();
isInit = true;
}
//获取单据号
public static long getTicketNo(){
try {
//获取许可证
semaphore.acquire();
long result = nextVal;
//每次加1
nextVal += 1;
//如果值超过了区间的上限,则需要进行更新(即重新计算区间范围)
if(nextVal >= limitVal){
update();
}
return result;
} catch (InterruptedException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
//释放
semaphore.release();
}
}
//更新数据库中配置信息
private static void update(){
//no.1 更新数据库中配置
SysConf newSysConf = DbData.update();
//no.2 更新完后,更改 limitVal , nextVal , n 的值
nextVal = newSysConf.getVal();
n = newSysConf.getN();
limitVal = newSysConf.getVal() + newSysConf.getInterval() * newSysConf.getN();
}
public static void main(String[] args) {
//初始化
TicketNoTool.init();
//定义3个线程来获取单据号
for(int i = 0 ; i < 3 ; i ++ ) {
final int j = i;
new Thread(() -> {
//每个线程拿22个单据号
int num = 0;
while (num++ < 22) {
long orderNo = TicketNoTool.getTicketNo();
System.out.println("Thread--"+j+" getTicketNo = " + orderNo);
}
}).start();
}
}
}