1. 并发(Concurrency)的例子
场景:单核计算机同时运行多个应用程序
假设你正在使用一台单核 CPU 的计算机,你同时打开了以下任务:
- 任务 A:听音乐(音乐播放器)。
- 任务 B:编写文档(Word 文档)。
- 任务 C:下载文件(浏览器)。
虽然只有一个 CPU 核心,这些任务似乎在“同时”进行,但实际上计算机通过快速切换任务来实现并发:
如何实现并发?
-
任务切片:
- CPU 分配时间片(如 10 毫秒)给每个任务。
- 时间片到期后,切换到另一个任务。
-
任务调度:
- 操作系统(如 Windows 或 Linux)通过调度器管理任务的执行顺序。
- 任务的切换对用户透明,用户感知不到切换过程。
具体流程:
- 时间片 1:CPU 执行任务 A(播放音乐)。
- 时间片 2:CPU 切换到任务 B(处理文档)。
- 时间片 3:CPU 切换到任务 C(下载文件)。
- 重复上述步骤。
尽管 CPU 在某一时刻只能执行一个任务,但因为切换速度极快(毫秒级),你会感觉这些任务在同时运行。
代码模拟:单线程并发任务
以下示例模拟单核 CPU 的任务切换:
public class ConcurrencyExample {
public static void main(String[] args) {
Runnable musicTask = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("Playing music...");
sleep(100);
}
};
Runnable documentTask = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("Editing document...");
sleep(100);
}
};
Runnable downloadTask = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("Downloading file...");
sleep(100);
}
};
// 按顺序执行任务,模拟任务切换
for (int i = 0; i < 5; i++) {
musicTask.run();
documentTask.run();
downloadTask.run();
}
}
private static void sleep(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
Playing music...
Editing document...
Downloading file...
Playing music...
Editing document...
Downloading file...
...
- 每个任务交替运行,给人感觉是“同时”执行的。
- 本质上是通过快速切换来模拟并发。
2. 并行(Parallelism)的例子
场景:多核计算机同时运行多个任务
现在假设你使用一台四核 CPU的计算机,仍然执行以下任务:
- 任务 A:听音乐(音乐播放器)。
- 任务 B:编写文档(Word 文档)。
- 任务 C:下载文件(浏览器)。
- 任务 D:视频压缩(FFmpeg)。
在多核环境下,不同任务可以真正同时运行,每个任务分配到一个 CPU 核心:
如何实现并行?
-
多核 CPU 的任务分配:
- CPU 核心 1:执行任务 A(音乐播放器)。
- CPU 核心 2:执行任务 B(文档编辑)。
- CPU 核心 3:执行任务 C(文件下载)。
- CPU 核心 4:执行任务 D(视频压缩)。
-
真正的同时运行:
- 多核系统中,每个核心可以独立处理任务。
- 这些任务在物理上同时运行,不需要时间片轮转。
代码模拟:多线程并行任务
以下代码使用多线程模拟多核 CPU 的并行执行:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ParallelismExample {
public static void main(String[] args) {
// 创建一个线程池,模拟 4 核 CPU
ExecutorService executor = Executors.newFixedThreadPool(4);
// 定义四个任务
executor.submit(() -> playMusic());
executor.submit(() -> editDocument());
executor.submit(() -> downloadFile());
executor.submit(() -> compressVideo());
executor.shutdown();
}
private static void playMusic() {
for (int i = 0; i < 5; i++) {
System.out.println("Playing music...");
sleep(100);
}
}
private static void editDocument() {
for (int i = 0; i < 5; i++) {
System.out.println("Editing document...");
sleep(100);
}
}
private static void downloadFile() {
for (int i = 0; i < 5; i++) {
System.out.println("Downloading file...");
sleep(100);
}
}
private static void compressVideo() {
for (int i = 0; i < 5; i++) {
System.out.println("Compressing video...");
sleep(100);
}
}
private static void sleep(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果(顺序可能不同):
Playing music...
Editing document...
Downloading file...
Compressing video...
Playing music...
Editing document...
Downloading file...
Compressing video...
...
- 每个任务分配到不同的线程,线程在多核 CPU 上真正同时运行。
- 输出顺序可能不同,取决于操作系统和线程调度器的工作方式。
3. 并发与并行的结合
场景:并发和并行的混合使用
假设:
- 你有 4 核 CPU。
- 运行 8 个任务(任务 A 到 H)。
操作系统会结合并发和并行来调度任务:
- 并行:
- 4 个任务同时运行在 4 核 CPU 上。
- 并发:
- 当某个任务阻塞时(如 I/O 操作),CPU 切换到其他任务继续执行。
示意图:
时间线: 核心 1 | A | | B | | A | | B |
核心 2 | C | | D | | C | | D |
核心 3 | E | | F | | E | | F |
核心 4 | G | | H | | G | | H |
4. 实际应用场景
场景 | 并发(Concurrency) | 并行(Parallelism) |
---|---|---|
浏览器加载网页 | 同时处理多个请求(图片、视频、CSS 文件) | 在多核 CPU 上并行渲染页面、解码视频和执行脚本。 |
视频播放 | 同时缓冲、解码和播放视频流(逻辑上的并发)。 | 使用 GPU 解码并渲染视频帧(物理上的并行)。 |
编译代码 | 同时编译多个模块(逻辑并发)。 | 使用多核 CPU 同时编译多个模块(物理并行)。 |
AI 训练 | 同时加载数据、更新参数(逻辑并发)。 | 使用 GPU 同时训练多个神经网络或并行处理不同的数据批次。 |
操作系统任务调度 | 多个任务共享 CPU 时间片,逻辑上的并发。 | 多核 CPU 上任务真正同时运行。 |
5. 总结
- 并发: 通过任务切换,多个任务在同一时间段内交替运行,强调任务管理和逻辑上的“同时进行”。
- 并行: 依赖多核或分布式系统,多个任务在物理上真正同时运行,强调性能和效率提升。