【多线程】屏障(Barrier)
【多线程】屏障(Barrier)
本文来自于我关于多线程系列文章。欢迎阅读、点评与交流
1.【多线程】互斥锁(Mutex)是什么?
2.【多线程】临界区(Critical Section)是什么?
3.【多线程】计算机领域中的各种锁
4.【多线程】信号量(Semaphore)是什么?
5.【多线程】信号量(Semaphore)常见的应用场景
6.【多线程】条件变量(Condition Variable)是什么?
7.【多线程】监视器(Monitor)是什么?
8.【多线程】什么是原子操作(Atomic Operation)?
9.【多线程】竞态条件(race condition)是什么?
10.【多线程】无锁数据结构(Lock-Free Data Structures)是什么?
11.【多线程】线程休眠(Thread Sleep)的底层实现
12.【多线程】多线程的底层实现
13.【多线程】读写锁(Read-Write Lock)是什么?
14.【多线程】死锁(deadlock)
15.【多线程】线程池(Thread Pool)
16.【多线程】忙等待/自旋(Busy Waiting/Spinning)
17.【多线程】屏障(Barrier)
18.【多线程硬件机制】总线锁(Bus Lock)是什么?
19.【多线程硬件机制】缓存锁(Cache Lock)是什么?
屏障(Barrier)是一种同步机制,它允许多个线程相互等待,直到所有参与线程都到达一个共同的执行点(即屏障点),然后这些线程才能继续执行。
一个生动的比喻:集体自驾游
想象一个由多辆车组成的自驾游车队,他们计划从不同地方出发,在第一个服务区(屏障点)集合。
- 规则是:必须所有车辆都到达服务区后,车队才能一起出发前往下一个目的地。
- 过程:
- 跑得快的车先到服务区,它必须停下来等待。
- 跑得慢的车后到服务区,它也同样停下来。
- 当最后一辆车也抵达服务区时,所有车辆才能同时重新发动,继续前行。
在这个比喻中:
- 每辆车 就是一个线程。
- 服务区 就是屏障。
- “所有车都到达” 这个条件就是释放条件。
屏障的正式定义与核心思想
屏障 用于协调多个并行线程,让它们在程序中的一个或多个特定点上进行同步。其核心思想是 “同进同退”——所有线程必须都到达屏障点,然后才能一起被释放,继续执行后续代码。如果有一个线程没到达,其他先到的线程都必须阻塞等待。
这与 锁 或 信号量 的同步模式有本质区别:
- 锁/信号量:通常用于保护临界区,保证同一时间只有一個/固定数量的线程能访问资源。关注的是 “互斥”。
- 屏障:用于保证所有线程都完成了一个阶段的工作,然后才能一起进入下一个阶段。关注的是 “协同”。
为什么需要屏障?它的用途是什么?
屏障主要用于解决 分阶段任务 的同步问题。
-
并行计算:
- 在很多并行算法中,一个任务可以被分成多个子任务由不同线程并行处理,但必须等所有子任务都完成后,才能进行下一阶段的计算或结果合并。
- 例子:并行排序、并行矩阵运算。
-
模拟系统:
- 在游戏或科学模拟中,需要计算所有物体在当前时刻的状态(并行计算),然后所有计算完成后,才能一起更新到下一时刻的世界状态。
-
数据预处理与后处理:
- 在主任务开始前,需要多个线程分别加载不同的资源(如游戏地图、角色模型、音效)。必须等所有资源都加载完毕(所有线程到达屏障),游戏才能开始。
- 任务结束后,可能需要多个线程一起清理资源。
一个简单的代码示例(使用 Java CyclicBarrier
)
Java中提供了一个非常经典的屏障实现:CyclicBarrier
。它的名字中的“Cyclic”意味着它可以循环使用——在所有等待线程被释放后,屏障会自动重置到初始状态,可以再次用于下一轮的同步。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;public class CyclicBarrierDemo {public static void main(String[] args) {// 1. 创建一个屏障,参与线程数为3,并定义一个“聚合点”后执行的任务int threadCount = 3;CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {// 当所有线程都到达屏障后,由最后一个到达的线程执行这个RunnableSystem.out.println("\n所有士兵都已到达集合点!继续向下一目标前进!\n");});// 2. 创建并启动多个士兵线程for (int i = 1; i <= threadCount; i++) {new Thread(new Soldier("士兵" + i, barrier)).start();}}static class Soldier implements Runnable {private final String name;private final CyclicBarrier barrier;public Soldier(String name, CyclicBarrier barrier) {this.name = name;this.barrier = barrier;}@Overridepublic void run() {try {// 模拟从不同地方赶往集合点System.out.println(name + " 正在赶往集合点...");Thread.sleep((long) (Math.random() * 5000)); // 模拟花费不同时间System.out.println(name + " 到达集合点!等待其他士兵...");// 在此等待其他线程barrier.await(); // 核心方法:等待直到所有线程都调用await()// 所有线程都到达屏障后,才会继续执行下面的代码System.out.println(name + " 开始执行后续任务!");} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}}
}
可能的运行结果:
士兵1 正在赶往集合点...
士兵2 正在赶往集合点...
士兵3 正在赶往集合点...
士兵1 到达集合点!等待其他士兵...
士兵3 到达集合点!等待其他士兵...
士兵2 到达集合点!等待其他士兵...所有士兵都已到达集合点!继续向下一目标前进!士兵2 开始执行后续任务!
士兵3 开始执行后续任务!
士兵1 开始执行后续任务!
关键点分析:
barrier.await()
是每个线程等待的方法。- 前两个到达的线程(如士兵1和3)会在此阻塞。
- 当最后一个线程(士兵2)调用
await()
时,屏障条件满足。- 首先,执行可选的
Runnable
任务(打印“所有士兵…”)。 - 然后,所有三个线程 被同时唤醒,继续执行它们
await()
方法之后的代码。
- 首先,执行可选的
屏障与另一个相似概念:闭锁
闭锁是另一种同步工具类,它允许一个或多个线程等待,直到在其他线程中执行的一系列操作完成。
特性 | 闭锁 | 屏障 |
---|---|---|
核心目标 | 等待事件发生 | 等待其他线程 |
状态 | 一次性。事件发生后,门闩永远打开。 | 可循环使用。自动重置,可用于下一阶段。 |
常用场景 | 等待资源初始化完成、等待所有服务启动 | 并行分阶段计算、迭代算法的同步 |
简单说:闭锁是“等人齐了开门”,开门后就不再关了;屏障是“每到一个检查点都要等人齐”,然后继续到下一个检查点。
总结
屏障 是一种强大的线程协同工具,它确保了并行任务在逻辑上的阶段性和整体性。通过让所有线程在特定点“集合”,它使得复杂的、需要分步协作的并发程序变得可控和有序。理解并善用屏障,是掌握高级多线程编程的又一关键步骤。