说明:Java & Go 并发编程序列的文章,根据每篇文章的主题或细分标题,分别演示 Java 和 Go 语言当中的相关实现。更多该系列文章请查看:Java & Go 并发编程系列
本文介绍 Java 和 Go 语言中如何实现运行多个并发任务,但只处理第一个已完成任务的结果的场景。
代码场景:假设有一道计算题1×1+2×2+3×3+…+10×10,分发给10个同学计算,谁先计算出来则采用该同学的计算结果。
「Java」ThreadPoolExecutor#invokeAny
该方法接收一个任务列表,然后运行任务,并返回第一个完成的执行结果。
/**
* CalculationTask 代表执行一个计算任务<br>
* 传入一个正整数 n 并返回 1×1+2×2+...+(n-1)×(n-1)+n×n
*/
static class CalculationTask implements Callable<Integer> {
private Integer number;
public CalculationTask(Integer number) {
this.number = number;
}
@Override
public Integer call() throws Exception {
// 随机模拟每个同学计算的时间
int randomMills = ThreadLocalRandom.current().nextInt(500);
TimeUnit.MILLISECONDS.sleep(randomMills);
int result = 0;
for (int i = 1; i <= number; i++) {
result += i * i;
}
// 以下语句只是为了打印计算过程的数学表达式
String expression =
IntStream.rangeClosed(1, number).boxed()
.map(i -> String.format("%d×%d", i, i))
.collect(Collectors.joining("+"));
System.out.printf("计算: %s = %d\n", expression, result);
return result;
}
}
public static void main(String[] args) {
// 创建一个固定线程数的线程池
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
// 生成10个计算任务集合并发执行,代表10个同学参与计算
List<CalculationTask> list =
IntStream
.rangeClosed(1, 10)
.boxed()
.map(number -> new CalculationTask(10))
.collect(Collectors.toList());
try {
// 将计算任务提交给线程池,并返回第一个已完成任务的结果
Integer result = executor.invokeAny(list);
System.out.printf("第一个返回的结果: %d\n", result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
// 关闭线程池
executor.shutdown();
System.out.println("End.");
}
运行结果:
计算: 1×1+2×2+3×3+4×4+5×5+6×6+7×7+8×8+9×9+10×10 = 385
第一个返回的结果: 385
End.
「Go」channel
利用缓冲通道来存放不同 Goroutine 的计算结果,从通道中接收第一个计算结果。
func main() {
// 创建一个容量为10的缓冲通道
bufferChan := make(chan int, 10)
for i := 1; i <= 10; i++ {
// 启用10个 Goroutine 模拟10个同学并发计算
go func() {
result := calculation(10)
// 将计算结果发送到通道
bufferChan <- result
}()
}
fmt.Printf("第一个返回的结果: %d\n", <-bufferChan)
fmt.Println("End.")
}
// 传入一个正整数 n 并返回 1×1+2×2+...+(n-1)×(n-1)+n×n
func calculation(number int) int {
// 随机模拟每个同学计算的时间
randomMills := rand.Intn(500)
time.Sleep(time.Millisecond * time.Duration(randomMills))
var result int
for i := 1; i <= number; i++ {
result += i * i
}
fmt.Printf("计算: 1×1+2×2+...+%d×%d = %d\n", number, number, result)
return result
}
运行结果:
计算: 1×1+2×2+...+10×10 = 385
第一个返回的结果: 385
End.
拓展
以上数学运算可以抽象为 map 和 reduce。
用 Python 实现如下:
from functools import reduce
li = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
total = reduce(lambda x, y: x + y, map(lambda x: x * x, li))
print(total) # 打印385
用 Java 实现如下:
Integer total = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.map(x -> x * x)
.reduce((x, y) -> x + y)
.get();
System.out.println(total); // 打印385
更多该系列文章请查看:Java & Go 并发编程系列