JAVA使用Lock实现多线程并发生成唯一的流水号

发布时间 2023-08-15 15:37:51作者: 我是一个邓疯子

今天在工作java开发过程中遇见需要生成十位数流水号的工作场景,本文将讲述下利用ReentrantLock实现多线程并发生成唯一的流水号的功能,有些情况可以采用数据库自定义序列号自增生成流水号,亦或是自己编写数据库触发器生成流水号。

但本文以代码为主,记录在代码层面上如何利用ReentrantLock,实现多线程同时请求时也能确保流水号取得唯一的场景。

ReentrantLock锁原理在本文中不再阐述,详细的讲解可参考此博主的讲解:

https://blog.csdn.net/zhengzhaoyang122/article/details/110847701

1.编写工具类生成流水号

ReentrantLock锁的运用其实很简单,在方法中如下添加即可,简单的调用业务前先上锁,调用完后释放锁。这里我采用简单的渠道号(四位)+年月日(8位)+流水号(10位)的规则生成业务使用的流水号。

public class SerialNumUtil {

    /**记录初始值*/
    public static int num = 0;

    private static ReentrantLock lock = new ReentrantLock();

    public static String getNum(String channelId) {
        String unique = "";
        //上锁
        lock.lock();
        try {
            //------------------流水号业务逻辑----------------
            //5位流水号
            if (num == 0) {
                //当天第一份流水单号
                unique = channelId + new SimpleDateFormat("yyyyMMdd").format(new Date()) + "0000000001";
            } else {
                //当天最后的订单流水号累加1
                String nums = String.valueOf(num + 1);
                //设定具体流水为十位数,单数则补齐前面的0
                StringBuilder sb = new StringBuilder(nums);
                for (int i = nums.length(); i < 10; i++) {
                    sb.insert(0, "0");
                }
                unique = channelId + new SimpleDateFormat("yyyyMMdd").format(new Date()) + sb.toString();
            }
            //已有流水单+1
            num++;
            //----------------------------------
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放锁
            lock.unlock();
        }
        return unique;
    }
}

2.编写测试类调用验证

这里我们利用CyclicBarrier类来模仿进行大量并发的测试。CyclicBarrier是JDK1.5的java.util.concurrent并发包中提供的一个并发工具类.
a.准备一个Task线程类

public class Task implements Runnable {
    private static final Logger logger = Logger.getLogger("log");
    private CyclicBarrier cyclicBarrier;

    public Task(CyclicBarrier cyclicBarrier) {
        this.cyclicBarrier = cyclicBarrier;
    }

    @Override
    public void run() {
        try {
            // 等待所有任务准备就绪
            cyclicBarrier.await();
            // 测试内容
            logger.info(SerialNumUtil.getNum("2050"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

b.主方法作为测试入口设定同时并发100个线程进行打印测试验证。

public class TestLock {
    public static void main(String[] args) {
        int count = 100;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(count);
        ExecutorService executorService = Executors.newFixedThreadPool(count);
        for (int i = 0; i < count; i++) {
            executorService.execute(new Task(cyclicBarrier));
        }
        executorService.shutdown();
        while (!executorService.isTerminated()) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

可以看到同一秒里,我们每条线程获取到的流水号都是唯一不重复的,所以完美实现了此功能!

本功能也是在网络上查找很多文章才实现的,感谢各位大佬!