type
Post
status
Published
date
Oct 12, 2025
slug
java1
summary
tags
技术探索
category
icon
password
很多人学 Java 并发,一上来就去看
synchronized、volatile、AQS、线程池参数,但真正写代码时还是容易混乱:为什么高并发不等于高并行?
为什么线程多了反而变慢?
为什么 Java 线程出问题,经常会牵扯到 CPU、内存、操作系统调度?
为什么守护线程不能用来处理核心业务任务?
Java 21 的虚拟线程是不是可以替代传统线程池?
这些问题看起来基础,但它们是理解 Java 并发体系的入口。
本文先从并发、并行、进程、线程、守护线程、协程和虚拟线程这些基础概念讲起。
一、串行、并发、并行有什么区别?
先看三个概念:串行、并发、并行。
1. 串行
串行就是任务一个接一个执行。
比如有三个任务:任务 A、任务 B、任务 C。
任务 A 执行完,任务 B 才能开始。任务 B 执行完,任务 C 才能开始。整个过程没有任务交替,也没有任务同时推进。
这种方式逻辑简单,但吞吐能力有限。
2. 并发
并发强调的是:在同一时间段内处理多个任务。
注意,并发不一定表示多个任务在同一时刻真正执行。
在单核 CPU 上,也可以有并发。
比如 CPU 先执行任务 A 一会儿,再切到任务 B,再切到任务 C。由于切换速度很快,从人的角度看,好像多个任务都在同时进行。
并发关注的是“同一时间段内能不能处理多个任务”。它强调的是任务交替推进的能力,而不是多个任务一定同时运行。
Java 后端服务中经常说“高并发”,本质上说的是系统在同一时间段内处理大量请求的能力,而不是每个请求都必须在同一时刻同时执行。
3. 并行
并行强调的是:多个任务在同一时刻真正同时执行。
并行通常依赖多核 CPU。
比如 4 核 CPU 可以在同一时刻让 4 个线程分别运行在 4 个核心上。
CPU Core 1 执行任务 A。CPU Core 2 执行任务 B。CPU Core 3 执行任务 C。CPU Core 4 执行任务 D。
所以可以这样理解:
串行:任务一个接一个执行。并发:同一时间段内处理多个任务。并行:同一时刻真正执行多个任务。
并发是一种处理能力,并行是一种执行方式。
二、高并发不等于线程越多越好
很多人刚接触并发时,会有一个误区:既然要提升系统吞吐,那是不是线程越多越好?
答案是否定的。线程本身是有成本的。线程多了以后,会带来几个问题:
第一,线程创建和销毁有成本。第二,线程切换有成本。第三,每个线程都需要栈内存。第四,线程太多会导致 CPU 花大量时间做上下文切换。第五,如果下游数据库、Redis、RPC 服务承受不了,线程越多只会把下游压垮。
所以并发编程的核心不是“多开线程”,而是:
用合理数量的线程,让 CPU、内存、IO、下游服务之间达到一个相对平衡的状态。
这也是为什么 Java 项目里通常不建议频繁手动
new Thread(),而是使用线程池统一管理线程资源。三、进程和线程的区别
理解 Java 并发,必须先理解进程和线程。
1. 进程是什么?
进程可以理解为一个正在运行的程序实例。
比如你启动一个 Java 应用,本质上就是启动了一个 JVM 进程。
进程拥有相对独立的资源,例如:
独立的地址空间。堆内存。文件句柄。网络连接。系统资源。
进程之间通常相互隔离,一个进程崩溃,一般不会直接导致另一个进程崩溃。
2. 线程是什么?
线程是 CPU 调度的基本单位。
一个进程中可以有多个线程。
这些线程共享进程中的一部分资源,比如堆内存、方法区、文件句柄等。
在 Java 中,一个 JVM 进程里可能有很多线程:
main 线程。业务线程。线程池线程。GC 线程。JIT 编译线程。定时任务线程。Netty IO 线程。监控上报线程。
每个线程也有自己独立的部分:
程序计数器。Java 虚拟机栈。本地方法栈。
可以简单总结为:
进程是资源分配单位。线程是 CPU 调度单位。一个进程可以包含多个线程。同一个进程内的多个线程共享进程资源。
四、为什么多线程会有安全问题?
根本原因是:多个线程共享同一块内存资源。
比如多个线程同时修改一个共享变量:
很多人以为
count++ 是一个动作,其实它至少包含三步:第一步,读取count的值。第二步,对count加 1。第三步,把新值写回count。
如果两个线程同时执行,就可能出现数据覆盖。
例如:
线程 A 读取到count = 0。线程 B 也读取到count = 0。线程 A 写回count = 1。线程 B 也写回count = 1。
两个线程都执行了一次加一,最终结果却是 1,而不是 2。
这就是多线程共享变量带来的典型问题。
Java 并发后面要解决的核心问题,其实就是三类:
原子性。可见性。有序性。
后续的
synchronized、volatile、CAS、AQS、线程池,本质上都是围绕这些问题展开。五、Java 线程和操作系统线程是什么关系?
传统 Java 线程通常和操作系统线程是一一对应的。
也就是说,你在 Java 中创建一个线程,底层通常会对应一个操作系统级线程。
例如:
调用
start() 后,JVM 会向操作系统申请创建线程,由操作系统参与调度。所以 Java 线程不是完全由 JVM 自己随便调度的,它和操作系统调度机制密切相关。
这也解释了为什么线程不是越多越好。
因为线程调度最终要依赖操作系统。线程数量过多时,操作系统的上下文切换成本会明显增加。
六、start 和 run 的区别
这是 Java 线程最基础但也最容易出错的点。
看下面代码:
如果直接调用
run(),它只是一个普通方法调用,不会开启新线程。真正开启新线程的是:
start() 会让线程进入可运行状态,然后由 JVM 和操作系统调度执行,最终再调用该线程的 run() 方法。可以这样总结:
run():普通方法调用,不创建新线程。start():启动新线程,由新线程执行run()。
还有一个细节:
同一个Thread对象只能调用一次start()。如果重复调用,会抛出IllegalThreadStateException。
七、什么是守护线程?
Java 线程分为两类:
用户线程。守护线程。
用户线程就是普通业务线程。
守护线程是服务型线程,比如 JVM 中的 GC 线程。
守护线程有一个重要特点:
当 JVM 中只剩下守护线程时,JVM 会直接退出。
也就是说,JVM 不会为了守护线程一直存活。
示例:
当主线程结束后,如果只剩这个守护线程,JVM 就会退出。
八、为什么核心业务不能交给守护线程?
因为守护线程不保证一定执行完成。
比如下面这些任务,都不适合交给守护线程:
订单落库。支付回调。消息确认。文件写入。数据同步。库存扣减。
原因很简单:如果 JVM 退出,守护线程可能还没来得及处理完任务,程序就结束了。
所以守护线程适合做一些后台服务型工作,但不适合承载必须完成的核心业务流程。
九、协程是什么?
协程可以理解为一种更轻量的执行单元。
传统线程通常由操作系统调度,而协程更多是在用户态由程序或运行时调度。
它的特点是:
创建成本更低。切换成本更低。可以用少量系统线程承载大量任务。
协程特别适合大量 IO 等待场景。
比如一个服务同时处理大量网络请求,每个请求大部分时间都在等数据库、Redis 或远程接口返回。
这种情况下,如果每个请求都占用一个操作系统线程,成本会比较高。
协程的思路是:
当一个任务阻塞等待时,把底层线程让出来,去执行其他任务。
十、Java 虚拟线程是什么?
Java 21 引入了虚拟线程。
虚拟线程可以看成 JVM 提供的一种轻量级线程。
它的目标是让开发者仍然用接近传统同步代码的方式编写程序,但底层可以更高效地承载大量阻塞任务。
示例:
虚拟线程特别适合:
大量阻塞 IO。请求第三方接口。数据库访问。文件读取。网络调用。
以前为了避免阻塞线程,代码里可能会出现复杂的异步回调、
CompletableFuture 编排、响应式写法。虚拟线程的优势在于:
很多场景下可以继续写直观的同步代码,同时降低传统线程的资源成本。
十一、虚拟线程能不能替代线程池?
不能简单这么理解。
虚拟线程适合大量阻塞 IO 场景,但不代表它可以解决所有并发问题。
如果任务是 CPU 密集型,比如:
大规模计算。图片处理。复杂加解密。大数据量排序。压缩解压。
这些任务本身就需要消耗 CPU。
即使换成虚拟线程,也不能让 CPU 变得更多。
CPU 密集型任务仍然要控制并发度,否则大量任务同时运行,只会造成 CPU 打满和频繁调度。
所以可以这样理解:
阻塞 IO 密集型:虚拟线程很有优势。CPU 密集型:仍然要控制并发数量。资源有限型:仍然要做限流和隔离。
虚拟线程降低的是线程资源成本,不是业务资源成本。
数据库连接数、Redis 连接数、下游接口承载能力,这些都不会因为使用虚拟线程而自动变大。
十二、Java 并发基础概念怎么串起来?
可以用一条线来理解:
高并发系统要处理大量任务。任务需要线程执行。线程运行在 JVM 进程中。Java 线程通常依赖操作系统线程调度。多个线程共享内存资源。共享资源会带来原子性、可见性、有序性问题。于是需要synchronized、volatile、CAS、AQS、线程池等机制。
也就是说,并发编程不是从锁开始的,而是从任务调度和资源共享开始的。
锁只是解决共享资源问题的一种手段。
十三、实际项目里应该注意什么?
1. 不要随意创建线程
不要在业务代码里频繁这样写:
更推荐使用统一管理的线程池。
线程池可以控制:
线程数量。队列大小。拒绝策略。线程名称。资源隔离。
2. 线程要有清晰命名
线上排查问题时,经常需要看线程栈。
如果线程名称都是默认的,比如
pool-1-thread-1、pool-1-thread-2,定位问题会比较痛苦。更好的方式是给线程池设置业务含义明确的名称,例如:
order-create-poolpayment-callback-pooluser-export-pool
这样一看线程栈,就知道问题大概出在哪个模块。
3. 不要让线程无限增长
线程数量要有上限。
如果线程无限创建,最终可能导致:
CPU 飙高。内存溢出。上下文切换严重。接口响应变慢。服务雪崩。
所以线程池必须设置合理的最大线程数和队列长度。
4. 不要在锁里做慢操作
比如:
这种写法风险很高。
因为 RPC、数据库查询、文件 IO 都可能很慢。
如果在持有锁的情况下执行这些操作,会导致其他线程长时间等待。
更好的做法是缩小锁的范围,只保护真正需要同步的临界区。
十四、总结
本文讲了 Java 并发中最基础的一组概念。
先记住这几个核心结论:
串行:任务一个接一个执行。并发:同一时间段内处理多个任务。并行:同一时刻真正执行多个任务。进程:资源分配单位。线程:CPU 调度单位。守护线程:JVM 不会等待它完成。协程:更轻量的用户态调度单元。虚拟线程:Java 提供的轻量级线程,适合大量阻塞 IO。
再进一步理解:
高并发不是线程越多越好。线程多了会带来调度、内存和资源成本。多线程共享内存会引出线程安全问题。Java 并发体系的核心,是解决共享资源访问的正确性和效率问题。
把这些基础概念理解清楚,再去看
synchronized、volatile、AQS、线程池,就不会只是背概念,而是能真正理解它们为什么存在、解决什么问题、适合用在什么场景。分享
