单据流水号,并发避免重复问题JAVA

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();
        }
    }
}
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值