前言
从1开始每次加1 加1000万次,你会怎么使用多线程的方式来加快计算速度,并且保证结果一致?这个问题我在以前校招的时候被问到过,当时一下懵逼。最近刚好体系学习了下多线程,趁热实现了一下。
代码案例
在这个场景里,如果多个线程对一个数据进行加1操作的话,那个数据就是一个热点数据,是要进行并发安全保护,也就是加锁,这样可能实际上并不会比单线程加来的快,因为创建线程,线程间等待锁释放的cpu调度也是需要耗费时间的。麻雀虽小考察点却不少,考察点如下:
- 需要把热点数据进行拆分,最后进行汇总,提高并发度。
- 如何进行数据量拆分,保证结果一致
- 你对并发这块的JUC的使用和熟悉情况
核心思路就是 把任务均分给多个线程,每个线程执行各自的加1操作,然后等每个线程执行完毕后,在主线程里挨个进行累加。
/**
* @author: lvzb31988
* @date: 2023/02/02 9:41
*/
@Slf4j
public class PlusTest {
public static class PlusTask implements Callable<Long> {
private Long range;
private Long num;
public PlusTask(Long range, Long num) {
this.range = range;
this.num = num;
}
@Override
public Long call() throws Exception {
for (int i = 0; i < range; i++) {
// 休眠一毫秒是为了模拟 加 1的这次操作耗时,不加的话执行太快了,无法看到明显的对比差异
Thread.sleep(1);
num++;
}
return num;
}
}
@Test
void onePlus() throws InterruptedException {
long start = System.currentTimeMillis();
// 1000W 太多了,这里给个1000,知道如何实现和看到明显对比即可
final int dataRange = 1000;
long total = 0;
for (int i = 0; i < dataRange; i++) {
// 休眠一毫秒是为了模拟 加 1的这次操作耗时,不加的话执行太快了,无法看到明显的对比差异
Thread.sleep(1);
total++;
}
// 耗时:1460 ms,total:1000
log.warn("耗时:{} ms,total:{}", System.currentTimeMillis() - start, total);
System.out.println("耗时:total: " + total);
}
/**
* 问题一: 均分
* 问题二: 为何线程池submit返回的 future get会报空指针
*
* @throws ExecutionException
* @throws InterruptedException
*/
@Test
void asyncOnePlus() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
// 数据总量
final long dataRange = 1000;
// 线程数
final int threadNum = 5;
// 均分数据总量
long avgRange = dataRange / threadNum;
List<Future<Long>> futureList = new ArrayList<>();
ExecutorService pool = Executors.newFixedThreadPool(threadNum);
for (int i = 1; i <= threadNum; i++) {
Future<Long> future = null;
if (i == threadNum) {
// 注意:需要特殊处理最后一个线程的处理量,不然分不完
future = pool.submit(new PlusTask(dataRange - (avgRange * (threadNum - 1)), 0L));
} else {
future = pool.submit(new PlusTask(avgRange, 0L));
}
futureList.add(future);
}
Long total = 0L;
for (int i = 0; i < futureList.size(); i++) {
Long num = futureList.get(i).get();
total += num;
}
// 耗时:284 ms,total:1000
log.warn("耗时:{} ms,total:{}", System.currentTimeMillis() - start, total);
}
}
结论
- 使用5个线程,单线程 耗时:1460 ms,5个线程 耗时:284 ms。可以多执行几次看看,设计合理基本上能快接近4-5倍的理论值。