1.问题背景
因为线上数据库存储过程在执行 “一次性大批量提交” 时失败,在无法升级数据库服务器的情况下,只能将一次大批量的提交任务进行分割,并且等待所有的分割任务都执行结束后再返回具体执行结果(此处也可以不返回,异步操作分割及调用数据库,因为可能有时候执行的时间较长,用户等待时间长、体验差);
具体的业务代码不方便展示,此处就将这部分内容简单展现,做一个小demo。
2.解决方案
/**
* main : 多线程+for循环+回调 测试demo
*
* @param args
* @return void
* @Author wanglx
* @Date 2023/3/10 16:01
*/
public static void main(String[] args) {
//1.定义测试数据
List<Integer> submitTestList = new ArrayList<>();
int submitMaxCount = 300;
for (int i=1;i<=12345;i++){
submitTestList.add(i);
}
//2.定义分割数字段
int beginInt;
int endInt;
int total = submitTestList.size();
int forCountOne = total % submitMaxCount;
int forCountTwo = total / submitMaxCount;
if (forCountOne != 0) {
forCountTwo = forCountTwo + 1;
}
//3.定义信号器等所需对象
CountDownLatch countDownLatch = new CountDownLatch(forCountTwo);
//定义随机数生成对象,此处仅用来模拟线程延迟,真实环境下请视情去除
Random rand = new Random();
/**
* 参数信息:
* int corePoolSize 核心线程大小
* int maximumPoolSize 线程池最大容量大小
* long keepAliveTime 线程空闲时,线程存活的时间
* TimeUnit unit 时间单位
* BlockingQueue<Runnable> workQueue 任务队列。一个阻塞队列
*/
ExecutorService executorService = new ThreadPoolExecutor(5, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
/**
*此处也可以直接使用谷歌的工具方法来进行分割,但是为了更便于大众理解,此处使用普通for循环来进行操作
* List<List<Integer>> partition = Lists.partition(submitTestList , submitMaxCount);
*/
for (int submitCount = 1; ; submitCount++) {
beginInt = (submitCount - 1) * submitMaxCount;
endInt = Math.min(submitCount * submitMaxCount, total);
List<Integer> integers = submitTestList.subList(beginInt, endInt);
executorService.execute(() -> submitTest(integers, countDownLatch,rand));
log.info(" 数量 :{},第{}次",integers.size(),submitCount);
if (submitCount * submitMaxCount > total) {
try {
boolean await = countDownLatch.await(60, TimeUnit.SECONDS);
if (!await) {
log.info("线程:{},批量提交失败,CountDownLatch 执行超时", Thread.currentThread().getName());
break;
}
} catch (InterruptedException e) {
log.error("线程:{},批量提交失败,CountDownLatch 执行异常", Thread.currentThread().getName());
break;
}
log.info("线程:{},批量提交结束,执行完成", Thread.currentThread().getName());
break;
}
}
}
/**
* submitTest : 提交方法(随机数生成和sleep是为了让每个线程都休眠1-3秒,
* 营造出批量提交数据时的延时效果,真实场景下当然不会这样啦。。)
* 业务逻辑基本上就是写在这个方法中的
*
* @param submits 提交内容集合
* @param countDownLatch 计数器
* @param rand 随机数生成类
* @return void
* @Author wanglx
* @Date 2023/3/10 16:16
*/
public static void submitTest(List<Integer> submits, CountDownLatch countDownLatch, Random rand) {
log.info("多线程提交XXX开始,ThreadName:{}",Thread.currentThread().getName());
int randNumber = (rand.nextInt(3) + 1) *1000;
sleep(randNumber);
log.info("多线程提交XXX结束,submits:{} ,randNumber :{} ", submits ,randNumber);
//计数-1是为了最终所有的线程任务执行结束后,同上方的 await 方法相呼应,使主线程能够执行下去
countDownLatch.countDown();
}
tips:
关于线程池(ThreadPoolExecutor)和信号器或者计数器(CountDownLatch,我习惯叫它信号器)的相关延伸知识,此处不做赘述,想了解的小伙伴们可以自行通过下方链接直达哦!
ThreadPoolExecutor:ThreadPoolExecutor详解
CountDownLatch:CountDownLatch详解
3.总结
本次是因为要处理线上问题才来研究这种线程用法的,通过研究也进一步加深了线程相关的知识,总体来讲算是劳有所得;
工作之后才发现,除非是真的热爱这个行业,否则大多数人终其一生也只是在编码入门线上摩擦,我如今亦如此;
通过写博文,希望可以通过“问题驱动学习”、“分享驱动学习”来督促、激励自已深耕码田。