【Java&Go并发编程系列】6.运行多个任务并处理所有结果——invokeAll VS channel

说明: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 并发编程系列

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值