说明:Java & Go 并发编程序列的文章,根据每篇文章的主题或细分标题,分别演示 Java 和 Go 语言当中的相关实现。更多该系列文章请查看:Java & Go 并发编程系列
本文介绍 Java 和 Go 语言中如何实现并发编程中等待一组并发任务完成的场景。
代码场景:假设有一道题目,同时分发给10个同学来完成,所有同学完成之后,老师公布答案。
「Java」CountDownLatch
使用 CountDownLatch 等待一组并发任务的完成,包含如下三要素:
- 一个初始值,即定义需要等待的任务的数目
- await() 需要等待并发任务先完成的线程调用
- countDown(),每个任务在完成的时候调用
public static void main(String[] args) throws InterruptedException {
// 声明初始计数为10
CountDownLatch countDownLatch = new CountDownLatch(10);
// 起10个线程模拟10个同学做题目
for (int i = 0; i < 10; i++) {
Thread t = new Thread(() -> {
// 生成从1-10的随机整数,模拟做题目花费的时间
int randomSecond = ThreadLocalRandom.current().nextInt(10) + 1;
try {
TimeUnit.SECONDS.sleep(randomSecond);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("第%s号同学用时%ds完成了题目
", Thread.currentThread().getName(), randomSecond);
// 计数减1
countDownLatch.countDown();
});
// 设置线程名称,用于打印对应的同学编号
t.setName(String.valueOf(i));
t.start();
}
// 阻塞直到 CountDownLatch 的计数为0
// 注意这里是 await 方法, 不是 wait 方法
countDownLatch.await();
System.out.printf("所有的同学都完成了题目,老师公布答案
");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
输出结果:
第2号同学用时2s完成了题目
第9号同学用时2s完成了题目
第8号同学用时2s完成了题目
第3号同学用时2s完成了题目
第5号同学用时4s完成了题目
第6号同学用时6s完成了题目
第1号同学用时8s完成了题目
第0号同学用时9s完成了题目
第4号同学用时9s完成了题目
第7号同学用时9s完成了题目
所有的同学都完成了题目,老师公布答案
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
「Go」sync.WaitGroup
使用 sync.WaitGroup 等待一组并发任务的完成,包含如下三要素:
- Add(),传入一个 int 类型的参数,增加计数器的数值
- Wait(),需要等待并发任务先完成的 goroutine 调用
- Done(),每个任务在完成的时候调用
func main() {
// sync.WaitGroup 是开箱即用的类型
var wg sync.WaitGroup
// 初始化计数器的值为10
wg.Add(10)
// 启用10个 goroutine 用于并发执行 go 函数
// goroutine,即用户级线程,也称协程,是 Go 语言并发编程模型中重要的三个元素 GPM 之一
for i := 0; i < 10; i++ {
go func(i int) {
randomSecond := rand.Intn(10) + 1
time.Sleep(time.Second * time.Duration(randomSecond))
fmt.Printf("第%d号同学用时%ds完成了题目
", i, randomSecond)
// 计数器减1
wg.Done()
}(i)
}
// 阻塞直到所有并发任务执行完成
wg.Wait()
fmt.Printf("所有的同学都完成了题目,老师公布答案
")
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
为了让程序的逻辑更严密,可以使用 defer
语句来保证 wg.Done()
一定被执行。
func main() {
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func(i int) {
// defer 语句用于延迟执行代码,在函数正常或异常结束的时刻执行
// 类似于 Java 的 finally
defer wg.Done()
randomSecond := rand.Intn(10) + 1
time.Sleep(time.Second * time.Duration(randomSecond))
fmt.Printf("第%d号同学用时%ds完成了题目
", i, randomSecond)
}(i)
}
wg.Wait()
fmt.Printf("所有的同学都完成了题目,老师公布答案
")
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
输出结果:
第2号同学用时1s完成了题目
第0号同学用时1s完成了题目
第6号同学用时2s完成了题目
第1号同学用时2s完成了题目
第8号同学用时6s完成了题目
第4号同学用时7s完成了题目
第5号同学用时8s完成了题目
第9号同学用时8s完成了题目
第7号同学用时9s完成了题目
第3号同学用时10s完成了题目
所有的同学都完成了题目,老师公布答案
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
拓展
「Java」CyclicBarrier
CyclicBarrier 可传入另一个 Runnable 对象,在所有线程都到达集合点后,将运行该 Runnable 对象,使用场景更广泛。
public static void main(String[] args) {
// 声明10参与者和结束时要运行的 Runnable 对象
// 这里只是打印了一行文字
CyclicBarrier cyclicBarrier = new CyclicBarrier(10,
() -> System.out.printf("所有的同学都完成了题目,老师公布答案
")
);
// 起10个线程模拟10个同学做题目
for (int i = 0; i < 10; i++) {
Thread t = new Thread(() -> {
// 生成从1-10的随机整数,模拟做题目花费的时间
int randomSecond = ThreadLocalRandom.current().nextInt(10) + 1;
try {
TimeUnit.SECONDS.sleep(randomSecond);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("第%s号同学用时%ds完成了题目
", Thread.currentThread().getName(), randomSecond);
try {
// 阻塞直到所有参与者都调用 await 方法
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
// 设置线程名称
t.setName(String.valueOf(i));
t.start();
}
System.out.printf("主线程执行结束
");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
输出结果:
主线程执行结束
第1号同学用时1s完成了题目
第5号同学用时1s完成了题目
第0号同学用时2s完成了题目
第3号同学用时4s完成了题目
第9号同学用时4s完成了题目
第2号同学用时5s完成了题目
第6号同学用时5s完成了题目
第4号同学用时6s完成了题目
第7号同学用时8s完成了题目
第8号同学用时10s完成了题目
所有的同学都完成了题目,老师公布答案
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
更多该系列文章请查看:Java & Go 并发编程系列