在Java后端开发面试中,并发多线程是高频核心考点,涵盖线程调度、上下文切换、锁机制、线程方法、死锁、可见性等关键内容。本文将系统性梳理并发线程核心知识点,剔除冗余内容,突出重点、易错点和面试考点,帮助大家快速吃透并发基础。
一、并发与并行基础认知
想要学好多线程,首先要明确并发、并行的核心区别,同时牢记操作系统底层执行规则:
- 核心前提:所有应用程序的任务,都无法绕过操作系统独立执行,线程的调度、执行、阻塞全部由OS管控。
- 线程调度队列 :线程就绪后会进入非公平就绪队列,由操作系统根据调度规则分配CPU时间片。
- 并发:同一时间段内,多个线程交替抢占CPU执行,宏观上同时运行,微观上串行执行。
- 并行:同一时刻,多个线程在多核CPU下同时执行,真正意义上的同时运行。
二、上下文切换
上下文切换是多线程并发中的核心性能问题,也是面试必考知识点,重点掌握定义、开销、测试工具和优化方案。
1. 什么是上下文切换?
CPU为了执行其他线程,暂停当前正在运行的线程,保存当前线程的运行状态(寄存器、程序计数器等上下文信息),再加载新线程的上下文并执行,这个过程就是线程上下文切换。
2. 上下文切换开销
开销区间:几毫秒到几十毫秒不等,开销大小随操作系统版本、硬件配置不同而变化。频繁的上下文切换会严重占用CPU资源,导致程序性能下降。
3. 开销度量工具
常用专业测试工具:Lmbench、vmstart,可精准测量系统上下文切换的耗时。
4. 如何减少上下文切换?
减少上下文切换是并发性能优化的关键,四种主流方案:
- 无锁并发编程:避免大量线程竞争锁,从根源减少线程阻塞与切换
- 使用CAS算法:乐观锁机制,无需阻塞线程,避免重量级锁带来的切换开销
- 控制线程数量:创建最少的必要线程,避免线程过多导致频繁抢占切换
- 使用协程:用户态轻量级调度,无需操作系统介入,切换开销远小于内核态线程
三、多线程适用场景
多线程并非适用于所有场景,IO密集型场景是多线程的最佳适用场景,而CPU密集型场景使用多线程收益极低。
核心适用场景:IO密集型任务
常见场景:网络请求、硬盘读写、文件操作、数据库查询等所有阻塞式IO操作。
核心作用:解决CPU资源浪费问题。IO任务的等待时间远大于CPU处理时间,单线程会导致CPU长时间空闲等待;多线程可以在线程IO阻塞时,切换执行其他任务,最大化利用CPU资源。
四、并发排查常用Linux指令
线上线程异常、死锁、卡顿问题排查,常用核心指令:grep、awk、sort、jstack
其中 jstack 是并发问题排查核心指令,主要用于抓取线程dump信息,可快速定位线程死锁、线程阻塞、死循环、线程挂起等线上问题。
五、线程核心方法与易错点对比
线程的休眠、等待、调度方法是面试易错重灾区,重点区分sleep 和 wait 的核心差异。
1. Thread类核心方法
- start() :调用后线程进入就绪队列,等待操作系统分配CPU时间片,不会立即执行
- join():阻塞当前主线程,必须等待调用join的子线程完全执行完毕后,主线程才能继续执行后续代码
- sleep() :线程主动休眠,立即让出CPU资源,且不会进入就绪队列,休眠期间操作系统不会为其分配时间片
2. sleep() 与 wait() 核心区别
两者都会让线程暂停执行、让出CPU资源,但锁的释放状态完全不同:
- sleep() :仅让出CPU,不释放锁,休眠时间结束后自动苏醒,重新进入就绪队列抢占CPU
- wait() :让出CPU,主动释放锁 ,线程进入阻塞队列,必须通过
notify()/notifyAll()手动唤醒
3. 线程阻塞队列特性
线程进入阻塞队列后,操作系统不会选中该线程执行任务,线程不会释放已持有的锁,这是极易混淆的考点。
六、synchronized 锁机制核心特性
synchronized是Java原生重量级锁,是保证线程安全的核心关键字,核心特性如下:
- 锁范围限制 :只能锁定引用类型对象,不支持基本数据类型
- 锁释放时机 :必须等待同步代码块执行完毕后才会释放锁;线程在同步代码块中睡眠、阻塞,都不会释放锁
- 线程安全级别 :保证原子性、可见性、有序性,读写操作全部线程安全
七、volatile 关键字核心特性
volatile是轻量级并发关键字,仅用于保证变量可见性,无法保证原子性,核心特点:
- 可见性定义:线程可以实时读取到内存中变量的最新值,避免变量缓存导致的数据不一致
- 线程安全特点 :读安全、写不安全
- 对比synchronized:volatile仅保证可见性和有序性,不保证原子性;synchronized实现全场景线程安全
八、死锁规避方案
多线程多锁嵌套场景极易出现死锁,死锁的核心是「互斥、持有等待、不可剥夺、循环等待」四大条件,规避思路就是打破任意一个条件,常用方案:
- 统一多把锁的获取顺序,避免循环等待
- 为锁操作设置超时时间,主动释放持有锁
- 减少锁嵌套场景,尽量只使用单锁
- 精简锁粒度,缩小同步代码块范围
九、核心知识点总结(速记)
- 上下文切换开销:几ms~几十ms,优化核心:少线程、无锁、CAS、协程
- 多线程适配:只适合IO密集型任务,优化CPU空转浪费
- 核心方法区别:sleep不释放锁,wait释放锁
- 锁安全:volatile读安全写不安全,synchronized读写全安全
- synchronized:锁引用类型,休眠不释放锁,代码块结束才解锁