0
0
0

Java 多线程:进程、线程与并发基础

文章摘要
|

一、进程与线程

1. 什么是进程?

进程(Process)是操作系统进行资源分配和管理的基本单位。

简单理解:当我们启动一个 Java 程序时,操作系统会为这个程序创建一个进程。

例如,启动 Spring Boot 项目:

java -jar application.jar

操作系统会启动一个 Java 进程,该进程拥有独立的内存地址空间以及相关系统资源,例如:

  • 堆内存

  • 打开的文件

  • 网络连接

  • 其他系统资源

可以简单理解为:进程就是一个正在运行的程序实例。

2. 什么是线程?

线程(Thread)是 CPU 调度和执行的基本单位。

一个进程可以包含多个线程。

例如,一个 Spring Boot 应用启动后,JVM 内部可能存在:

  • main 主线程

  • GC 垃圾回收线程

  • JIT 编译线程

  • HTTP 请求处理线程

  • 其他业务线程

这些线程都属于同一个 Java 进程。

同一个进程中的多个线程可以共享进程资源,但每个线程都有自己的执行状态和线程栈。

3. 进程与线程的区别

对比维度

进程

线程

定义

程序运行实例

CPU 调度执行单位

资源

拥有独立资源

共享进程资源

内存

地址空间相互独立

共享进程内存

创建成本

相对较高

相对较低

通信

通常需要 IPC

可以通过共享内存通信

崩溃影响

通常影响自身进程

线程异常不一定导致整个进程退出

需要注意:

一个线程发生未捕获异常时,通常会导致该线程终止,但不意味着整个 JVM 进程一定退出。

二、线程之间的内存结构

这是理解多线程线程安全问题的重要基础。

JVM 运行时数据区中,主要有以下几个与线程相关的内存区域:

内存区域

是否线程共享

作用

程序计数器 PC

不共享

记录当前线程执行的字节码位置

Java 虚拟机栈

不共享

存放方法调用的栈帧、局部变量等

本地方法栈

不共享

支持 Native 方法执行

堆

共享

存放对象实例、数组等

方法区

共享

存放类信息、运行时常量池等

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. 并发与并行的区别

对比

并发

并行

定义

同一时间段交替推进

同一时刻同时执行

单核 CPU

可以

不可以

多核 CPU

可以

可以

是否真正同时执行

不一定

是

重点记住:

并发强调多个任务的交替推进,并行强调多个任务真正同时执行。

四、为什么 Java 后端需要多线程?

通过一个 Spring Boot 接口的实际场景理解。

假设一个接口需要查询三个服务:

任务

耗时

查询用户信息

100ms

查询订单信息

200ms

查询商品信息

300ms

1. 串行执行

User user = userService.getUser();              // 100ms

Order order = orderService.getOrder();          // 200ms

Product product = productService.getProduct();  // 300ms

三个任务依次执行,总耗时:

100 + 200 + 300 = 600ms

2. 并发执行

将三个相互独立的任务交给不同线程执行:

线程 A:查询用户信息       100ms

线程 B:查询订单信息       200ms

线程 C:查询商品信息       300ms

等待三个任务全部完成。

理想情况下,总耗时约为:

max(100, 200, 300) = 300ms

实际还需要考虑线程调度、任务提交、网络通信等开销。

因此,并发执行可以缩短多个独立 IO 任务的整体等待时间。

3. 多线程不一定能提高性能

多线程并不意味着一定比单线程快,需要根据任务类型进行分析。

IO 密集型任务

例如:

  • 查询 MySQL

  • 调用第三方 HTTP 接口

  • 读取文件

  • 等待 Redis 响应

这类任务经常需要等待 IO 完成。

在等待期间,可以让其他线程执行任务,从而提高 CPU 和系统资源的利用率。

CPU 密集型任务

例如:

  • 大量数学计算

  • 图片处理

  • 数据压缩

  • 加密解密

这类任务主要消耗 CPU。

如果 CPU 核心数量有限,创建过多线程反而会增加上下文切换成本,降低性能。

因此,生产环境需要根据任务类型、CPU 核数和资源情况合理设置线程数量。

五、重点总结

  1. 进程:操作系统进行资源分配和管理的基本单位,可以理解为一个正在运行的程序实例。

  2. 线程:CPU 调度和执行的基本单位,一个进程可以包含多个线程。

  3. 同一个进程中的多个线程可以共享堆内存和方法区,但每个线程拥有独立的 Java 虚拟机栈。

  4. 并发:多个任务在同一时间段内交替推进。

  5. 并行:多个任务在同一时刻真正同时执行。

  6. 单核 CPU 可以实现并发,但不能让两个线程真正同时执行 CPU 指令。

  7. IO 密集型任务适合利用多线程提高资源利用率,但多线程不保证一定提高性能。

  8. 多线程访问共享变量时,必须考虑线程安全问题。

六、常见问题与答案

1. 一个 Java 进程可以创建多少个线程?

没有固定数量限制,受到操作系统资源、内存、线程栈大小等因素限制。

2. 同一个进程中的两个线程可以共享变量吗?

可以。例如静态变量、实例成员变量以及共享对象中的属性,都可能被多个线程访问。

但共享访问不代表线程安全。

3. 为什么多个线程会出现线程安全问题?

因为多个线程可能同时读取和修改同一个共享数据,导致数据竞争、可见性问题和执行顺序问题。

4. 单核 CPU 能不能实现多线程?

可以。操作系统通过线程调度和时间片切换,让多个线程交替执行。

5. 多线程是不是越多越好?

不是。线程过多会增加内存占用、上下文切换成本,还可能造成 CPU 竞争和系统资源耗尽。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

评论