JAVA使用Lock实现多线程并发生成唯一的流水号
今天在工作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();
}
}
}
}
可以看到同一秒里,我们每条线程获取到的流水号都是唯一不重复的,所以完美实现了此功能!