说明:Java & Go 并发编程序列的文章,根据每篇文章的主题或细分标题,分别演示 Java 和 Go 语言当中的相关实现。更多该系列文章请查看:Java & Go 并发编程系列
本文介绍 Java 和 Go 语言中如何实现运行多个并发任务,并处理所有任务的返回结果的场景。
代码场景:假设有一道计算题1×1+2×2+3×3+…+10×10,为了更快算出结果,将该计算任务拆分成10个求平方数的子任务,分给10个同学分别计算,A同学领到1×1,B同学领到2×2,以此类推。最终将10个同学的结果累加得到最终的结果。
「Java」ThreadPoolExecutor#invokeAll
该方法接收一个 Callable 列表,等待所有任务完成之后,返回一个 Future 列表。
/**
* SquareTask 代表执行一个求平方数任务,传入一个数字并返回该数字的平方数
*/
static class SquareTask implements Callable<Integer> {
private Integer number;
public SquareTask(Integer number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
// 随机模拟每个同学计算的时间
int randomMills = ThreadLocalRandom.current().nextInt(500);
TimeUnit.MILLISECONDS.sleep(randomMills);
Integer result = number * number;
System.out.printf("计算: %d × %d = %d\n", number, number, result);
return result;
}
}
public static void main(String[] args) {
// 创建一个固定线程数的线程池
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
// 生成10个计算任务集合,代表10个同学参与计算求平方数任务中的一项
List<SquareTask> list =
IntStream
.rangeClosed(1, 10)
.boxed()
.map(number -> new SquareTask(number))
.collect(Collectors.toList());
try {
List<Future<Integer>> futureList = executor.invokeAll(list);
int total = 0;
List<Integer> resultList = new ArrayList<>();
for (Future<Integer> future : futureList) {
Integer result = future.get();
resultList.add(result);
total += result;// 将每个同学的计算结果累加
}
// 以下语句只是为了打印计算过程的数学表达式
String expression = resultList.stream().map(String::valueOf).collect(Collectors.joining(" + "));
System.out.printf("汇总结果: %s = %d\n", expression, total);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
executor.shutdown();
System.out.println("End.");
}
输出结果:
计算: 5 × 5 = 25
计算: 4 × 4 = 16
计算: 2 × 2 = 4
计算: 7 × 7 = 49
计算: 10 × 10 = 100
计算: 8 × 8 = 64
计算: 1 × 1 = 1
计算: 6 × 6 = 36
计算: 3 × 3 = 9
计算: 9 × 9 = 81
汇总结果: 1 + 4 + 9 + 16 + 25 + 36 + 49 + 64 + 81 + 100 = 385
End.
「Go」channel
利用缓冲通道来存放不同 Goroutine 的计算结果,从通道中接收计算结果汇总。
func main() {
totalItem := 10
// 创建一个容量为10的缓冲通道
bufferChan := make(chan int, totalItem)
for i := 1; i <= totalItem; i++ {
// 启用10个 Goroutine 模拟10个同学并发计算
go func(i int) {
result := square(i) // 每个同学只做一项求平方数
bufferChan <- result // 将计算结果发送到通道
}(i)
}
var total int
resultArr := make([]string, totalItem)
// 循环10次从通道中接收计算结果
for i := 0; i < totalItem; i++ {
result := <-bufferChan // 从通道取出每个同学的计算结果
resultArr[i] = strconv.Itoa(result) // int 转 string
total += result // 将每个同学的计算结果累加
}
fmt.Printf("汇总结果: %s = %d\n", strings.Join(resultArr, " + "), total)
fmt.Println("End.")
}
// 计算一个数的平方数
func square(number int) int {
// 随机模拟每个同学计算的时间
randomMills := rand.Intn(500)
time.Sleep(time.Millisecond * time.Duration(randomMills))
result := number * number
fmt.Printf("计算: %d × %d = %d\n", number, number, result)
return result
}
输出结果:
计算: 9 × 9 = 81
计算: 10 × 10 = 100
计算: 2 × 2 = 4
计算: 6 × 6 = 36
计算: 3 × 3 = 9
计算: 7 × 7 = 49
计算: 5 × 5 = 25
计算: 1 × 1 = 1
计算: 8 × 8 = 64
计算: 4 × 4 = 16
汇总结果: 81 + 100 + 4 + 36 + 9 + 49 + 25 + 1 + 64 + 16 = 385
End.
可以看到 Java 的实现方式中,汇总结果做了一个排序。这是因为在ThreadPoolExecutor#invokeAll,返回 Future 列表的顺序跟传入的 Callable 列表顺序是一一对应的。
更多该系列文章请查看:Java & Go 并发编程系列