🔔 时效提示:本文内容基于2026年4月最新技术动态撰写,涵盖JDK 17/21并发机制与虚拟线程演进。建议收藏本文,随时查阅核心知识点与面试考点。
在Java后端开发的面试与实战中,并发编程是绕不开的核心知识点,更是区分“会用框架”和“真正理解技术”的分水岭。如今,电工ai助手已成为众多开发者查阅技术资料、攻克知识难点的得力工具——本文将带你系统梳理Java并发编程的全貌,从基础概念到底层原理,再到高频面试题,助你理清逻辑、看懂示例、记住考点,建立完整的并发知识链路。

一、痛点切入:为什么一定要学Java并发编程?
传统单线程开发的痛点

想象一个简单的电商系统:用户下单时需要同时处理库存扣减、订单生成、积分更新、消息推送等多个操作。如果使用单线程串行执行,用户支付后可能需要等待数秒甚至更久才能看到“支付成功”的页面——这是完全不可接受的。
单线程开发存在以下核心问题:
CPU利用率低:I/O操作(如数据库查询、文件读写)耗时较长,CPU在此期间处于空闲等待状态,严重浪费计算资源
响应速度慢:一个耗时操作阻塞整个流程,影响用户体验
无法充分利用多核CPU:现代服务器普遍配备多核CPU,单线程无法利用多核并行处理能力
多线程带来的新挑战
引入多线程后,程序性能大幅提升,但同时也带来了新的问题:
线程安全问题:多个线程同时修改共享变量,导致数据不一致
可见性问题:一个线程修改了变量,其他线程“看不到”这个修改
有序性问题:编译器和CPU为了性能会重排序指令,多线程环境下可能导致意想不到的结果
死锁与活锁:线程之间相互等待资源,程序陷入“假死”状态
本文将从最基础的概念讲起,逐步深入底层原理,帮你彻底吃透Java并发编程。
二、核心概念讲解:进程与线程
进程(Process)
定义:进程是操作系统资源分配的基本单位,是程序的一次运行实例。
通俗理解:启动一个Java程序,操作系统就会创建一个进程,为它分配独立的内存空间、文件句柄等资源。比如你同时打开了一个浏览器和一个IDEA编辑器,这就是两个独立的进程,它们之间的资源基本隔离。
线程(Thread)
定义:线程是CPU调度的最小单位,是进程内的一个独立执行流。
通俗理解:一个进程可以包含多个线程,这些线程共享进程内的资源(如内存空间),每个线程负责执行一部分任务。比如浏览器进程内部,可能有渲染页面的线程、处理网络请求的线程、监听用户输入的线程——它们“同时”工作,给用户流畅的体验。
核心区别对比
| 对比维度 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 独立的内存空间和系统资源 | 共享进程的内存空间 |
| 调度单位 | 资源分配单位 | CPU调度单位 |
| 通信方式 | 复杂(IPC、网络通信) | 简单(共享内存即可通信) |
| 切换开销 | 较大(需切换内存映射、文件句柄等) | 较小(只需切换栈和寄存器) |
| 独立性 | 进程之间相互独立,一个崩溃不影响其他进程 | 线程间共享内存,一个线程崩溃可能导致整个进程崩溃 |
线程之所以比进程更轻量,根本原因在于:进程切换需要更换内存映射和虚拟地址空间,涉及TLB刷新等操作;而线程切换仅需保存和恢复少量寄存器内容,开销小得多-1。
三、关联概念讲解:并发(Concurrency)与并行(Parallelism)
并发(Concurrency)
定义:在同一时间段内,多个任务交替执行的能力。
关键理解:并发强调的是“多个任务在宏观上同时进行,微观上交替进行”。在单核CPU下,任务调度器将CPU时间片分给不同线程轮流使用,由于时间片极短(毫秒级),用户感觉多线程在“同时运行”——这就是并发。
并行(Parallelism)
定义:在同一时刻,多个任务同时执行的能力。
关键理解:并行需要多核CPU的硬件支持。在多核处理器上,每个核心可以独立执行一个线程,真正实现“同一时刻多个线程都在运行”。
一句话区分
引用Rob Pike(Go语言创始人)的一句经典论述:并发是关于同一时间应对多件事的能力,并行是关于同一时间动手做多件事的能力-1。
| 对比维度 | 并发 | 并行 |
|---|---|---|
| 核心 | 交替执行 | 同时执行 |
| 硬件要求 | 单核/多核均可 | 必须多核 |
| 关注点 | 任务结构设计 | 执行效率提升 |
| 比喻 | 一个人同时吃三碗饭(轮流吃) | 三个人各吃一碗饭(同时吃) |
四、Java线程的底层实现原理
Java线程模型的演进
Java线程的底层实现并非一成不变,而是随着JDK版本持续演进,主要分为三大模型-5:
用户级线程(User-Level Threads) :由JVM完全自主管理调度,不依赖操作系统内核。早期JDK曾采用GreenThread实现,上下文切换开销极低,但无法利用多核CPU算力,现已淘汰。
内核级线程(Kernel-Level Threads) :每个Java线程对应一个操作系统内核线程,由内核负责调度。这是当前主流JDK版本的默认实现(1:1映射模型),能够充分利用多核CPU的并行算力-5。
混合线程模型:JVM维护用户态线程池,再映射到少量内核线程上执行,兼顾灵活性与效率。
平台线程 vs 虚拟线程(JDK 21+)
从JDK 21开始,Java正式引入了虚拟线程(Virtual Threads) 技术,这是Java并发领域的重大变革:
平台线程(Platform Thread) :传统Java线程,与OS内核线程一一对应,占用内核资源,不可无限扩展,线程数达到内核限制会引发OOME-
虚拟线程(Virtual Thread) :由JVM管理的用户态轻量级线程,基于Continuation(延续性)机制实现,遇到I/O阻塞时自动将调用栈保存到堆中,释放载体线程去执行其他任务-
生产实践建议:I/O密集型任务优先使用虚拟线程,CPU密集型任务继续使用平台线程池。
JVM跨平台适配机制
JVM针对不同操作系统内核调整线程实现细节:在Linux系统中,JVM通过Pthread库创建内核线程;在Windows系统中,则调用Win32 API完成内核线程的创建与调度-5。这种标准化适配机制,让开发者无需关注操作系统底层差异,即可实现跨平台的并发应用开发。
五、核心锁机制:synchronized vs Lock
synchronized——JVM内置锁
定义:synchronized是Java语言层面的关键字,由JVM层面实现的隐式锁(无需手动释放),是使用最广泛、最基础的线程同步机制-12。
三种使用方式
public class SynchronizedDemo { // 1. 实例方法锁:锁当前实例对象 public synchronized void objectLock() { System.out.println("获取对象锁"); } // 2. 静态方法锁:锁当前类的Class对象 public static synchronized void classLock() { System.out.println("获取类锁"); } // 3. 代码块锁:自定义锁对象(灵活度最高) private final Object lockObj = new Object(); public void blockLock() { synchronized (lockObj) { System.out.println("获取代码块锁"); } } }
锁升级机制(JDK 1.6+核心优化)
synchronized在JDK 1.6后引入了锁升级机制,大幅提升了性能。升级路线为:无锁 → 偏向锁 → 轻量级锁 → 重量级锁-11-。
| 锁状态 | 适用场景 | 实现原理 |
|---|---|---|
| 无锁 | 无任何线程竞争 | 初始状态 |
| 偏向锁 | 始终被同一线程获取 | 对象头Mark Word记录线程ID |
| 轻量级锁 | 少量线程交替竞争 | CAS + 自旋 |
| 重量级锁 | 多线程激烈竞争 | Monitor + 阻塞 |
⚠️ 重要提示:在JDK 15及以后版本中,偏向锁默认已被禁用,因此经典升级路径简化为:无锁 → 轻量级锁 → 重量级锁-。
synchronized核心特点
可重入:同一线程可重复获取同一把锁
非公平:默认不保证等待线程按请求顺序获取锁
自动释放:方法或代码块执行完自动解锁,无需手动处理
底层依赖:对象头的Mark Word + 监视器锁(ObjectMonitor)-12
Lock——JUC显式锁
定义:JUC包下java.util.concurrent.locks.Lock接口的实现类,需要手动加锁/解锁,是synchronized的补充和增强-12。
import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockDemo { // 公平锁(按请求顺序获取),默认非公平锁(性能更高) private static final ReentrantLock lock = new ReentrantLock(true); public static void doTask() { lock.lock(); // 加锁 try { // 业务操作 System.out.println(Thread.currentThread().getName() + "获取锁"); } finally { lock.unlock(); // 必须放finally,否则死锁 } } }
核心对比
| 对比维度 | synchronized | ReentrantLock |
|---|---|---|
| 实现层面 | JVM关键字 | Java代码层面 |
| 锁释放 | 自动释放(执行完代码块/方法) | 手动释放(必须unlock) |
| 可中断性 | 不支持 | 支持lockInterruptibly() |
| 超时获取 | 不支持 | 支持tryLock(timeout) |
| 公平锁 | 不支持(默认非公平) | 支持new ReentrantLock(true) |
| 锁升级优化 | 支持(JDK 1.6+) | 不支持 |
六、volatile关键字:轻量级同步利器
定义与核心特性
volatile是Java并发编程中的轻量级同步关键字,由JMM(Java Memory Model)提供内存语义支持。其核心作用:-21-22
✅ 保证可见性:一个线程修改volatile变量后,其他线程立即看到修改
✅ 禁止指令重排序:编译器/CPU不会对volatile变量的读写进行重排序
❌ 不保证原子性:复合操作(如i++)仍需synchronized或Atomic类
可见性问题演示
// 不加volatile,线程B可能永远无法退出循环 private static boolean flag = false; // 不加volatile Thread threadB = new Thread(() -> { while (!flag) { } // 线程B的工作内存中flag一直是false System.out.println("线程B检测到flag变为true"); }); threadB.start(); Thread.sleep(100); flag = true; // 线程A修改flag
在这个例子中,如果flag不加volatile修饰,线程B可能永远无法跳出while循环,因为它的工作内存中flag一直是false-22。
volatile底层原理
可见性实现:被volatile修饰的变量,修改时会立即刷新回主内存,并通过MESI缓存一致性协议让其他CPU核心的工作内存缓存失效,强制从主内存重新读取-21。
有序性实现:JVM通过内存屏障(Memory Barrier) 禁止指令重排序:
写屏障:写volatile后,禁止后续指令重排到写操作前
读屏障:读volatile前,禁止前面指令重排到读操作后
典型应用场景
状态标志位:
volatile boolean stop = false;控制线程停止双重检查锁(DCL)单例:必须加volatile防止指令重排序导致的对象半初始化问题
轻量级状态更新:无需原子性保证的简单状态变更
与synchronized的区别
一句话记忆:volatile解决的是可见性问题,不解决原子性问题;synchronized同时保证可见性、有序性和原子性,但开销更大。
七、Java内存模型(JMM)与happens-before规则
JMM的核心定义
JMM(Java Memory Model,Java内存模型) 定义了线程与主内存、工作内存的交互规则,解决并发编程中的三大核心问题-21:
可见性:一个线程修改共享变量,其他线程能立即感知
有序性:禁止编译器/CPU的指令重排序优化
原子性:操作不可中断,是最小执行单元
JMM的核心约束
JMM规定:每个线程都有自己的工作内存(Working Memory,可理解为CPU缓存),共享变量存储于主内存(Main Memory,物理内存)中。线程对共享变量的所有操作必须在工作内存中进行——先从主内存读取变量到工作内存,修改后再刷新回主内存。这种模型正是可见性问题的根源。
happens-before规则
happens-before是JMM的核心:如果动作A happens-before动作B,则A的执行结果对B是可见的,且A不会被重排序到B之后。happens-before是一个传递关系(若A hb B且B hb C,则A hb C)-48。
你必须记住的核心规则:
| 规则名称 | 具体内容 |
|---|---|
| 程序次序规则 | 同一线程内,按程序顺序的前操作hb后操作 |
| Monitor锁规则 | 对同一monitor,unlock(h) hb后续lock(h) |
| volatile规则 | 对volatile变量的写hb该变量后续的读 |
| 传递性 | 若A hb B且B hb C,则A hb C |
| 线程启动规则 | Thread.start()前的操作hb新线程的运行开始 |
| 线程终止规则 | 被join的线程中的操作hb join返回之后的操作 |
关键结论:只要写操作没有通过某种happens-before关系“连接”到读操作,JMM就允许可见性失效与重排序-48。
八、AQS:JUC并发框架的核心基石
AQS是什么?
AQS(AbstractQueuedSynchronizer,抽象队列同步器) 是JUC包的灵魂,位于java.util.concurrent.locks包中,是一个用于构建锁和同步器的抽象框架-39。从ReentrantLock到CountDownLatch,从Semaphore到CyclicBarrier,几乎所有常用的同步工具类都构建在AQS之上。
一句话理解:AQS提供了一个基于FIFO阻塞队列 + 原子状态管理的框架,让你能轻松实现各种自定义的同步工具-41。
AQS的核心组件
1. 同步状态:volatile int state
state是AQS的核心变量,用于表示同步状态-39:
state=0:表示无锁状态state>0:表示有锁状态,数值可表示重入次数由
volatile修饰,保证多线程间的可见性
不同子类通过state表达自己的业务含义:
ReentrantLock:0=未锁,1=已锁(重入次数)
Semaphore:表示剩余许可数量
CountDownLatch:表示倒计数值
2. CLH虚拟双向队列
AQS使用一个双向链表来管理等待线程,每个节点(Node)包含:
thread:等待的线程waitStatus:节点状态(SIGNAL=-1表示需要被唤醒,CANCELLED=1表示线程取消)prev/next:前后指针
工作流程:当线程获取锁失败时,会被封装成Node节点加入队列尾部并阻塞;当锁释放时,会唤醒队列中的后继节点-41。
两种同步模式
| 模式 | 说明 | 典型实现 |
|---|---|---|
| 独占模式(Exclusive) | 同一时间只有一个线程能获取锁 | ReentrantLock |
| 共享模式(Shared) | 同一时间多个线程能获取锁 | CountDownLatch、Semaphore、ReentrantReadWriteLock的读锁 |
AQS工作流程简析
获取锁(独占模式) :
tryAcquire() → 成功则获取锁 ↓ 失败 addWaiter() → 封装Node入队 ↓ acquireQueued() → 自旋尝试获取,失败则park()阻塞
释放锁(独占模式) :
tryRelease() → 成功释放 ↓ unparkSuccessor() → 唤醒后继节点
核心考点:AQS采用模板方法设计模式,子类只需实现tryAcquire()、tryRelease()、tryAcquireShared()、tryReleaseShared()等“钩子方法”,AQS框架负责处理阻塞、排队、唤醒等底层细节-41。
九、CAS无锁并发机制
CAS是什么?
CAS(Compare-And-Swap,比较并交换) 是一种硬件级别的原子操作原语,是乐观锁的核心实现。其核心语义:针对内存地址V,给定旧预期值A与新值B,当且仅当V的当前值等于A时,才将V的值原子更新为B,整个操作不可中断-58。
区别于synchronized等悲观锁的“先加锁再操作”,CAS采用“先验证再更新”的无锁思路,在低并发场景下大幅降低线程调度与上下文切换的开销-58。
CAS底层原理
CPU层面:CAS的原子性本质是CPU硬件层面的指令支持。以X86_64架构为例,核心是CMPXCHG指令(比较并交换指令),但该指令本身不具备多核原子性,必须搭配LOCK前缀才能实现多核环境下的原子操作-58:
锁定对应的内存地址的缓存行(基于MESI协议)
禁止该指令与前后的读写指令重排序
刷新写缓冲区,保证操作结果对所有CPU核心立即可见
JDK层面:Java通过jdk.internal.misc.Unsafe类(JDK9前为sun.misc.Unsafe)的native方法暴露CAS操作-58:
// Unsafe类的CAS核心方法 public final native boolean compareAndSetInt(Object o, long offset, int expected, int x); public final native boolean compareAndSetLong(Object o, long offset, long expected, long x); public final native boolean compareAndSetReference(Object o, long offset, Object expected, Object x);
AtomicInteger实战示例
import java.util.concurrent.atomic.AtomicInteger; public class CASTest { // AtomicInteger底层使用CAS实现无锁更新 private static AtomicInteger counter = new AtomicInteger(0); public static void increment() { // incrementAndGet内部是CAS自旋循环 // 期望值=当前值,新值=当前值+1 // 如果CAS失败(被其他线程抢先修改),则循环重试直到成功 int result = counter.incrementAndGet(); System.out.println("新值:" + result); } public static void main(String[] args) { // 10个线程同时递增,最终结果保证为10 for (int i = 0; i < 10; i++) { new Thread(CASTest::increment).start(); } } }
ABA问题与解决方案
ABA问题:CAS操作存在一种特殊情况——一个值从A变为B再变回A,CAS操作会认为值没有发生变化,但实际中间发生过变更-。
解决方案:使用带版本号的原子类AtomicStampedReference或带标记位的AtomicMarkableReference,引入版本号或标记位,确保即使值相同,只要版本号或标记位不同,CAS操作也会失败。
| 原子类 | 适用场景 | 解决方式 |
|---|---|---|
| AtomicInteger | 普通计数器 | 仅比较值 |
| AtomicStampedReference | 需跟踪修改次数 | 比较值+版本号 |
| AtomicMarkableReference | 需标记是否修改过 | 比较值+布尔标记 |
十、JUC核心组件一览
JUC(java.util.concurrent)包是Java并发编程的核心工具库,包含多个强大的并发处理组件--29:
| 类别 | 核心组件 | 核心作用 |
|---|---|---|
| 原子类 | AtomicInteger、AtomicLong、AtomicReference | 无锁CAS实现的线程安全变量 |
| 锁 | ReentrantLock、ReentrantReadWriteLock、StampedLock | 更灵活的显式锁控制 |
| 线程池 | ExecutorService、ThreadPoolExecutor | 线程复用与管理 |
| 同步器 | CountDownLatch、CyclicBarrier、Semaphore | 线程间协调控制 |
| 阻塞队列 | LinkedBlockingQueue、ArrayBlockingQueue | 生产者-消费者模式 |
| 并发集合 | ConcurrentHashMap、CopyOnWriteArrayList | 线程安全的集合 |
| 异步编程 | CompletableFuture、Future | 异步任务编排 |
十一、高频面试题与参考答案
面试题1:synchronized和ReentrantLock的区别是什么?
参考答案:
实现层面:synchronized是JVM关键字,由JVM层面实现;ReentrantLock是JUC包中的Java类,由JDK实现
锁释放:synchronized自动释放(方法/代码块执行完);ReentrantLock需在finally块中手动unlock()
功能特性:ReentrantLock支持可中断锁(lockInterruptibly)、超时获取锁(tryLock)、公平锁;synchronized不支持
性能:JDK 1.6后synchronized引入锁升级机制,低竞争场景下性能与ReentrantLock相近
面试题2:volatile能保证原子性吗?为什么?
参考答案:
不能。volatile只保证可见性和禁止指令重排序,不保证原子性。原因在于复合操作(如i++)包含“读取→修改→写入”三步,volatile无法使这三步成为不可分割的原子操作。需要原子性时应使用synchronized或Atomic原子类。
面试题3:AQS的核心设计思想是什么?
参考答案:
AQS采用模板方法设计模式,核心是volatile int state同步状态 + CLH FIFO等待队列:
通过
state变量表示资源状态,子类通过tryAcquire()/tryRelease()等方法自定义获取/释放逻辑获取失败的线程被封装成Node节点加入等待队列并阻塞
释放资源时唤醒队列中的后继节点
支持独占(如ReentrantLock)和共享(如CountDownLatch)两种模式
面试题4:CAS的ABA问题是什么?如何解决?
参考答案:
ABA问题是CAS操作中的一种特殊情况:一个值从A变为B再变回A,CAS会认为值没有发生变化,但实际上中间发生过变更。
解决方案:使用带版本号的原子类AtomicStampedReference或带标记位的AtomicMarkableReference,在比较值的同时比较版本号/标记位,只有两者都匹配时才更新成功。
面试题5:什么是happens-before原则?请列举3条常见规则。
参考答案:
happens-before是JMM的核心,用于判断一个操作的结果是否对另一个操作可见。常见规则:
程序次序规则:同一线程内,按程序顺序的前操作happens-before后操作
volatile规则:对volatile变量的写happens-before该变量后续的读
Monitor锁规则:对同一monitor,unlock() happens-before后续的lock()
十二、结尾总结
核心知识点回顾
本文系统梳理了Java并发编程的完整知识链路,核心要点如下:
| 层次 | 核心知识点 | 掌握要求 |
|---|---|---|
| 基础概念 | 进程/线程、并发/并行 | 必须掌握,面试必考 |
| 线程安全 | synchronized锁升级、volatile可见性 | 底层原理需理解 |
| 内存模型 | JMM、happens-before规则 | 理解即可,面试能说明白 |
| 框架机制 | AQS、CAS、JUC组件 | 核心原理需掌握 |
| 实战应用 | 线程池、原子类、并发集合 | 熟练使用 |
进阶学习建议
源码阅读:建议阅读ReentrantLock、AtomicInteger、ConcurrentHashMap的核心源码
JDK 21新特性:关注虚拟线程(Virtual Threads)在生产环境的落地方案
实战项目:在电商、秒杀等高并发场景中实际运用所学知识
下一篇预告
下一期将深入讲解JDK 21虚拟线程的底层实现机制,对比Go协程与Rust异步架构的优劣,以及虚拟线程在生产环境的大规模落地方案。敬请期待!
本文由电工ai助手协助整理,旨在帮助开发者系统掌握Java并发编程核心知识。如有错误与不足,欢迎指正交流。