Java并发编程实战、顺序号_java生成顺序增长序列(1)

三、并发技术以及数据库表设计

使用到的并发技术

ConcurrentHashMap        线程安全的Map

ThreadLocal                      每个线程保存一份它自己的副本

Semaphore                        信号量

CountDownLatch              闭锁

数据库表设计

表名 prefix_sequence

prefix_name            varchar(255)           主键              前缀编码名字

width                       int                            主键              宽度

date_flag                 int                            主键              是否带日期(0否,1是)

sequence_value      varchar(255)                                 顺序号

sequence_date        datetime                                      日期

四、思路和代码实现

基础的顺序号实现

基础实体类,dao,service代码

/**

  • 前缀顺序号实体类
    */
    @Data
    public class PrefixSequence {

// 前缀名字
private String prefixName;

// 宽度
private Integer width;

// 是否带日期(0否,1是)
private Integer dateFlag;

// 顺序号
private String sequenceValue;

// 日期
private Date sequenceDate;

}

/**

  • 顺序号mapper
    */
    public interface PrefixSequenceMapper {

int deleteByPrimaryKey(@Param(“prefixName”) String prefixName, @Param(“width”) Integer width, @Param(“dateFlag”) Integer dateFlag);

int insert(PrefixSequence record);

int insertSelective(PrefixSequence record);

PrefixSequence selectByPrimaryKey(@Param(“prefixName”) String prefixName, @Param(“width”) Integer width, @Param(“dateFlag”) Integer dateFlag);

int updateByPrimaryKeySelective(PrefixSequence record);

int updateByPrimaryKey(PrefixSequence record);
}

xml文件

<?xml version="1.0" encoding="UTF-8"?> prefix_name, width, date_flag, sequence_value, sequence_date select from prefix_sequence where prefix_name = #{prefixName,jdbcType=VARCHAR} and width = #{width,jdbcType=INTEGER} and date_flag = #{dateFlag,jdbcType=INTEGER} delete from prefix_sequence where prefix_name = #{prefixName,jdbcType=VARCHAR} and width = #{width,jdbcType=INTEGER} and date_flag = #{dateFlag,jdbcType=INTEGER} insert into prefix_sequence (prefix_name, width, date_flag, sequence_value, sequence_date) values (#{prefixName,jdbcType=VARCHAR}, #{width,jdbcType=INTEGER}, #{dateFlag,jdbcType=INTEGER}, #{sequenceValue,jdbcType=VARCHAR}, #{sequenceDate,jdbcType=TIMESTAMP}) insert into prefix_sequence prefix_name, width, date_flag, sequence_value, sequence_date, #{prefixName,jdbcType=VARCHAR}, #{width,jdbcType=INTEGER}, #{dateFlag,jdbcType=INTEGER}, #{sequenceValue,jdbcType=VARCHAR}, #{sequenceDate,jdbcType=TIMESTAMP}, update prefix_sequence sequence_value = #{sequenceValue,jdbcType=VARCHAR}, sequence_date = #{sequenceDate,jdbcType=TIMESTAMP}, where prefix_name = #{prefixName,jdbcType=VARCHAR} and width = #{width,jdbcType=INTEGER} and date_flag = #{dateFlag,jdbcType=INTEGER} update prefix_sequence set sequence_value = #{sequenceValue,jdbcType=VARCHAR}, sequence_date = #{sequenceDate,jdbcType=TIMESTAMP} where prefix_name = #{prefixName,jdbcType=VARCHAR} and width = #{width,jdbcType=INTEGER} and date_flag = #{dateFlag,jdbcType=INTEGER}

/**

  • 前缀顺序号服务
    */
    public interface PrefixSequenceService {

/**

  • 生成带日期的顺序号
  • @param prefixName
  • @param width
  • @return
    */
    String generateSequenceDateValue(String prefixName, Integer width) throws Exception;

/**

  • 1 生成带日期的线程安全的顺序号
  • @param prefixName
  • @param width
  • @return
  • @throws Exception
    */
    String generateSynSequenceDateValue(String prefixName, Integer width) throws Exception;

/**

  • 2 生成带日期的线程安全的顺序号 相同的前缀名加锁,不同的前缀名可以并行
  • @param prefixName
  • @param width
  • @return
  • @throws Exception
    */
    String generateSynKeyLockSequenceDateValue(String prefixName, Integer width) throws Exception;

}

/**

  • 前缀顺序号服务实现
    */
    @Service
    public class PrefixSequenceServiceImpl implements PrefixSequenceService {

@Autowired
PrefixSequenceMapper prefixSequenceMapper;

@Override
public String generateSequenceDateValue(String prefixName, Integer width) throws Exception{
if(StringUtils.isEmpty(prefixName)||null == width){
throw new Exception(“参数不能为空”);
}
// 查询数据库顺序号
PrefixSequence prefixSequence = prefixSequenceMapper.selectByPrimaryKey(prefixName,width,1);
// 如果没有就新建
if(prefixSequence==null){
String newSequence = generateNewSequence(prefixName,width,1);
SimpleDateFormat dateFormat=new SimpleDateFormat(“yyyyMMdd”);
String format = dateFormat.format(new Date());
return prefixName+“-”+format+“-”+newSequence;
}else{
SimpleDateFormat dateFormat=new SimpleDateFormat(“yyyyMMdd”);
// 如果是当天,递增顺序号更新并返回
if(SequenceStringUtil.judgeSameDay(prefixSequence)){
// 将顺序号转换为Long
Long value = Long.valueOf(prefixSequence.getSequenceValue());
String updateValue = SequenceStringUtil.formatSequenceValue(width,++value);
prefixSequence.setSequenceValue(updateValue);
prefixSequenceMapper.updateByPrimaryKeySelective(prefixSequence);
// 返回更新后的顺序号
String updateSequence = prefixSequence.getPrefixName()+“-”+
dateFormat.format(prefixSequence.getSequenceDate())+“-”+
prefixSequence.getSequenceValue();
return updateSequence;
}else{
// 如果不是当天,更新顺序号(更新当前日期,如果宽度为5,顺序号为00001)并返回。
String newSequence = SequenceStringUtil.formatSequenceValue(width, 1L);
prefixSequence.setSequenceValue(newSequence);
prefixSequence.setSequenceDate(new Date());
prefixSequenceMapper.updateByPrimaryKeySelective(prefixSequence);
// 返回更新后的顺序号
String updateSequence = prefixSequence.getPrefixName()+“-”+
dateFormat.format(prefixSequence.getSequenceDate())+“-”+
prefixSequence.getSequenceValue();
return updateSequence;
}
}
}

/**

  • 辅助方法
  • 创建一个新的顺序号,指定宽度,从1开始
  • @param prefixName
  • @param width
  • @param dateFlag
  • @return
    */
    private String generateNewSequence(String prefixName, Integer width, Integer dateFlag) {
    // 创建一个新的顺序号,指定宽度,从1开始
    String newSequence = SequenceStringUtil.formatSequenceValue(width, 1L);
    // 保存顺序号
    PrefixSequence prefixSequence = new PrefixSequence();
    prefixSequence.setPrefixName(prefixName);
    prefixSequence.setWidth(width);
    prefixSequence.setSequenceValue(newSequence);
    prefixSequence.setSequenceDate(new Date());
    prefixSequence.setDateFlag(dateFlag);
    prefixSequenceMapper.insertSelective(prefixSequence);
    // 返回新生成的顺序号
    return newSequence;
    }
    }

/**

  • 顺序号工具类
  • 使用 StringBuilder 以及 DecimalFormat 填充前缀0
    */
    public class SequenceStringUtil {

// 填充0并格式化值
public static String formatSequenceValue(Integer sequenceWidth, Long value){

StringBuilder sb = new StringBuilder();
for(int i=0;i<sequenceWidth;i++){
sb.append(“0”);
}

DecimalFormat decimalFormat = new DecimalFormat(sb.toString());
String resultValue = decimalFormat.format(value);

return resultValue;
}

//判断是否为同一天
public static boolean judgeSameDay(PrefixSequence sequence) {
SimpleDateFormat dateFormat=new SimpleDateFormat(“yyyyMMdd”);
return dateFormat.format(sequence.getSequenceDate()).equals(dateFormat.format(new Date()));
}
}

生成顺序号核心逻辑

a) 根据前缀名,宽度,是否带日期查询数据库中的顺序号。

b) 如果没有找到,生成新的顺序号(比如宽度为5,从00001开始)保存并返回。

c) 如果已经有该前缀以及宽度的顺序号,如果日期相同,将顺序号递增,更新并返回。

d) 如果已经有该前缀以及宽度的顺序号,如果日期不相同,更新日期(当天)以及初始化顺序号

(比如宽度为5,从00001开始)并返回。

基础的顺序号服务就开发完了,我们来测试一下。

@RestController
@RequestMapping(“/sequence”)
public class PrefixSequenceController {

@Autowired
PrefixSequenceService prefixSequenceService;

/**

  • 生成带日期的顺序号
  • @param prefixName
  • @param width
  • @return
  • @throws Exception
    */
    @GetMapping(“/generateSequenceDateValue/{prefixName}/{width}”)
    public AjaxResult generateSequenceDateValue(@PathVariable(“prefixName”) String prefixName, @PathVariable(“width”) Integer width) throws Exception {
    return AjaxResult.success(“成功”,prefixSequenceService.generateSequenceDateValue(prefixName,width));
    }

}

单次请求返回结果大致如下

AAAA-20230721-00001

AAAA-20230721-00002

AAAA-20230721-00003

BBBB-20230721-00001

BBBB-20230721-00002

返回了预期的结果。

下面我们模拟高并发场景下的顺序号生成

@RestController
@RequestMapping(“/sequence”)
public class PrefixSequenceController {

@Autowired
PrefixSequenceService prefixSequenceService;

/**

  • 高并发下测试顺序号
  • @param prefixName
  • @param width
  • @return
  • @throws Exception
    */
    @GetMapping(“/test/generateSequenceDateValue/{prefixName}/{width}”)
    public AjaxResult testGenerateSequenceDateValue(@PathVariable(“prefixName”) String prefixName, @PathVariable(“width”) Integer width) throws Exception {
    CountDownLatch countDownLatch = new CountDownLatch(100);
    class PrefixSequenceRun implements Runnable {

@SneakyThrows
@Override
public void run() {
countDownLatch.countDown();
countDownLatch.await();
String sequence = prefixSequenceService.generateSequenceDateValue(prefixName,width);
System.out.println(sequence);
}
}
for(int i=0;i<100;i++){
new Thread(new PrefixSequenceRun()).start();
}
return AjaxResult.success();
}

}

使用闭锁模拟100次同时请求顺序号服务(前缀为AAAA,宽度为5)。

输出大致结果如下

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注网络安全获取)
img

写在最后

在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。

需要完整版PDF学习资源私我

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

:vip204888 (备注网络安全获取)**
[外链图片转存中…(img-ELgEJudp-1712498732146)]

写在最后

在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。

需要完整版PDF学习资源私我

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-D5b2nQPx-1712498732146)]

  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值