Java 多线程:进程、线程与并发基础
一、进程与线程
1. 什么是进程?
进程(Process)是操作系统进行资源分配和管理的基本单位。
简单理解:当我们启动一个 Java 程序时,操作系统会为这个程序创建一个进程。
例如,启动 Spring Boot 项目:
java -jar application.jar操作系统会启动一个 Java 进程,该进程拥有独立的内存地址空间以及相关系统资源,例如:
堆内存
打开的文件
网络连接
其他系统资源
可以简单理解为:进程就是一个正在运行的程序实例。
2. 什么是线程?
线程(Thread)是 CPU 调度和执行的基本单位。
一个进程可以包含多个线程。
例如,一个 Spring Boot 应用启动后,JVM 内部可能存在:
main 主线程
GC 垃圾回收线程
JIT 编译线程
HTTP 请求处理线程
其他业务线程
这些线程都属于同一个 Java 进程。
同一个进程中的多个线程可以共享进程资源,但每个线程都有自己的执行状态和线程栈。
3. 进程与线程的区别
需要注意:
一个线程发生未捕获异常时,通常会导致该线程终止,但不意味着整个 JVM 进程一定退出。
二、线程之间的内存结构
这是理解多线程线程安全问题的重要基础。
JVM 运行时数据区中,主要有以下几个与线程相关的内存区域:
Java 8 中,HotSpot JVM 使用元空间(Metaspace)实现方法区。
重点:线程共享与线程私有
线程共享:
堆内存
方法区
线程私有:
程序计数器
Java 虚拟机栈
本地方法栈
例如:
public class ThreadMemoryDemo {
// 静态变量,多个线程可以共享访问
private static int count = 0;
public static void main(String[] args) {
// 线程 A
new Thread(() -> {
int a = 10;
count++;
}).start();
// 线程 B
new Thread(() -> {
int b = 20;
count++;
}).start();
}
}这里:
count是静态变量,多个线程可以共享访问。a是线程 A 的局部变量。b是线程 B 的局部变量。每个线程都有自己的栈帧和局部变量空间。
需要注意,count++ 并不是原子操作。
它实际上包含读取、计算、写入三个步骤。
当多个线程同时执行时,可能出现数据竞争,导致最终结果不符合预期。
这也是后续学习 synchronized、volatile、CAS 和 Atomic 原子类的基础。
三、并发与并行
并发和并行是多线程中两个非常重要的概念。
1. 什么是并发?
并发(Concurrency):多个任务在同一时间段内交替执行。
例如:
假设 CPU 只有一个核心,现在有两个线程 A 和 B。
CPU 可以通过时间片轮转的方式,让两个线程交替执行。
时间轴:
线程 A:执行 ---- 执行
线程 B: 执行 ---- 执行
线程 A: 执行
线程 B: 执行虽然两个线程看起来像是同时运行,但实际上,单核 CPU 在同一时刻只能执行一个线程的 CPU 指令。
CPU 通过快速切换线程,让多个任务在一段时间内都能推进。
这就是并发。
2. 什么是并行?
并行(Parallelism):多个任务在同一时刻真正同时执行。
例如:
假设 CPU 有两个核心:
CPU 核心 1 执行线程 A。
CPU 核心 2 执行线程 B。
两个核心可以同时执行不同线程的指令。
这就是并行。
3. 并发与并行的区别
重点记住:
并发强调多个任务的交替推进,并行强调多个任务真正同时执行。
四、为什么 Java 后端需要多线程?
通过一个 Spring Boot 接口的实际场景理解。
假设一个接口需要查询三个服务:
1. 串行执行
User user = userService.getUser(); // 100ms
Order order = orderService.getOrder(); // 200ms
Product product = productService.getProduct(); // 300ms三个任务依次执行,总耗时:
100 + 200 + 300 = 600ms2. 并发执行
将三个相互独立的任务交给不同线程执行:
线程 A:查询用户信息 100ms
线程 B:查询订单信息 200ms
线程 C:查询商品信息 300ms等待三个任务全部完成。
理想情况下,总耗时约为:
max(100, 200, 300) = 300ms实际还需要考虑线程调度、任务提交、网络通信等开销。
因此,并发执行可以缩短多个独立 IO 任务的整体等待时间。
3. 多线程不一定能提高性能
多线程并不意味着一定比单线程快,需要根据任务类型进行分析。
IO 密集型任务
例如:
查询 MySQL
调用第三方 HTTP 接口
读取文件
等待 Redis 响应
这类任务经常需要等待 IO 完成。
在等待期间,可以让其他线程执行任务,从而提高 CPU 和系统资源的利用率。
CPU 密集型任务
例如:
大量数学计算
图片处理
数据压缩
加密解密
这类任务主要消耗 CPU。
如果 CPU 核心数量有限,创建过多线程反而会增加上下文切换成本,降低性能。
因此,生产环境需要根据任务类型、CPU 核数和资源情况合理设置线程数量。
五、重点总结
进程:操作系统进行资源分配和管理的基本单位,可以理解为一个正在运行的程序实例。
线程:CPU 调度和执行的基本单位,一个进程可以包含多个线程。
同一个进程中的多个线程可以共享堆内存和方法区,但每个线程拥有独立的 Java 虚拟机栈。
并发:多个任务在同一时间段内交替推进。
并行:多个任务在同一时刻真正同时执行。
单核 CPU 可以实现并发,但不能让两个线程真正同时执行 CPU 指令。
IO 密集型任务适合利用多线程提高资源利用率,但多线程不保证一定提高性能。
多线程访问共享变量时,必须考虑线程安全问题。
六、常见问题与答案
1. 一个 Java 进程可以创建多少个线程?
没有固定数量限制,受到操作系统资源、内存、线程栈大小等因素限制。
2. 同一个进程中的两个线程可以共享变量吗?
可以。例如静态变量、实例成员变量以及共享对象中的属性,都可能被多个线程访问。
但共享访问不代表线程安全。
3. 为什么多个线程会出现线程安全问题?
因为多个线程可能同时读取和修改同一个共享数据,导致数据竞争、可见性问题和执行顺序问题。
4. 单核 CPU 能不能实现多线程?
可以。操作系统通过线程调度和时间片切换,让多个线程交替执行。
5. 多线程是不是越多越好?
不是。线程过多会增加内存占用、上下文切换成本,还可能造成 CPU 竞争和系统资源耗尽。
