1.多线程核心知识梳理:从进程关系到实践要点

作为一名程序员,多线程是并发编程的基础,也是面试和工作中的高频考点。这篇文章整理自我的学习笔记,核心内容参考了《并发编程实战》《计算机原理》等文献,可能与部分书籍内容重合。它并非零基础入门教程,更适合用于梳理知识体系、复习核心要点,若能帮到同样在巩固多线程知识的你,我会十分荣幸。

一、线程与进程:分清这对 "搭档"

理解多线程,首先要搞懂线程与进程的关系 ------ 进程是线程的 "容器",二者既有联系又有明确区别,从四个核心维度就能清晰区分:

对比维度 进程 线程
粒度 操作系统资源分配的基本单位 CPU调度和执行的基本单位
内存 占有独立内存空间,进程间内存隔离 共享所属进程的内存空间
稳定性 单个进程异常不影响其他进程 单个线程崩溃可能导致整个进程异常
消耗 创建 / 销毁需分配回收资源、处理页调度,开销大 仅需保存寄存器和栈信息,开销小

为什么进程开销比线程大?

关键在于 "资源管理" 的差异:

  1. 创建 / 终止效率:进程需初始化内存管理、文件管理等资源信息,线程直接共享这些信息,因此线程创建 / 终止更快;
  2. 切换效率:同一进程的线程共享虚拟内存和页表,切换时无需更换页表;而进程切换需重新加载页表,开销显著更高;
  3. 数据交互效率:线程间共享进程内存,数据传递无需经过内核;进程间通信则需依赖内核机制(如管道、消息队列),效率更低。

二、线程的 "一生":从新建到终止

线程的生命周期包含 7 个状态,每个状态的切换都有明确触发条件,搞懂这些就能掌握线程的运行逻辑:

  1. New(新建) :在代码中创建线程对象(如new Thread()),JVM 为其分配内存、初始化成员变量,但此时还未分配 CPU 执行权,不能运行;
  2. Runnable(就绪) :调用start()方法后,JVM 为线程创建方法调用栈和程序计数器,线程进入 "等待 CPU" 的就绪状态;此外,sleep()结束、join()结束、线程拿到对象锁等场景,也会让线程进入就绪状态;
  3. Running(运行) :就绪状态的线程获得 CPU 时间片,开始执行run()方法中的代码,此时处于运行状态;
  4. Blocked(阻塞) :运行中的线程因 "获取同步锁失败" 进入阻塞状态(比如进入synchronized代码块时,锁已被其他线程占用),会被放入锁池的_EntryList队列等待;
  5. Waiting(等待) :线程执行wait()join()park()等方法后,会进入_WaitSet队列,需等待其他线程调用notify()/notifyAll()唤醒;
  6. Waiting_Timeout(超时等待) :调用带超时时间的方法(如wait(1000)sleep(1000)),线程会在等待超时后自动唤醒,无需依赖其他线程通知;
  7. Terminated(终止) :线程正常执行完run()方法,或因异常中断,生命周期彻底结束。

创建线程的 4 种方法,该怎么选?

实际开发中,创建线程主要有 4 种方式,各有适用场景:

  • 继承 Thread 类 :重写run()方法,调用start()启动。优点是在run()中可直接用this获取当前线程;缺点是 Java 不支持多继承,扩展性受限,且每次新建任务需创建独立线程,损耗较大。
  • 实现 Runnable 接口 :实现run()方法,将实例传给Thread对象。优点是任务与线程解耦,同一任务可传给多个Thread,适合多线程执行同一任务的场景。
  • 实现 Callable 接口 :实现call()方法,支持返回执行结果(需配合FutureTask),且能抛出异常,适合需要获取线程执行结果的场景。
  • 使用线程池 / 定时器 :如ExecutorsThreadPoolExecutor,能复用线程、减少创建 / 销毁开销,是生产环境的首选方式。

三、线程中断:别用 "暴力停止"

停止线程是个技术活,直接用stop()"杀死" 线程的方式已被弃用,正确的做法是通过 "通知" 让线程自行终止。

3 种停止线程的方式

  1. 使用 volatile 退出标志 :定义volatile boolean isStop = false,线程在run()中循环判断isStop,外部线程修改isStop = true时,线程正常退出。volatile保证标志的可见性,避免线程读取到旧值。
  2. 弃用的 stop () 方法 :调用stop()会强制终止线程,且不会执行finally中的清理操作(如关闭文件、数据库连接),还可能导致线程持有的锁无法释放,引发死锁,绝对不能用。
  3. 推荐的 interrupt () 方法:这不是 "强制停止",而是给线程打一个 "中断标志",让线程自行判断是否退出,更安全灵活。

interrupt () 与 stop () 的核心区别

特性 stop() interrupt()
作用方式 强制杀死线程 仅设置中断标志,通知线程
锁释放 不释放持有的锁 不直接释放锁,线程可自行处理
安全性 易导致资源泄漏、死锁 安全,由线程自主控制退出时机

正确使用 interrupt () 的 3 种场景

  1. 线程处于sleep()/wait()/join()状态:调用interrupt()会让线程立即退出阻塞,抛出InterruptedException,捕获异常后可执行退出逻辑;
  2. 线程处于 I/O 阻塞(如 Socket 读取):会抛出ClosedByInterruptException,同样通过捕获异常处理中断;
  3. 线程正常运行:需通过Thread.interrupted()isInterrupted()判断中断标志,若标志为true,则主动退出循环。

3 个中断相关方法的区别

  • interrupt():实例方法,给线程设置中断标志,不直接停止线程;
  • interrupted():静态方法,判断 "当前线程" 是否被中断,会清除中断标志
  • isInterrupted():实例方法,判断 "调用该方法的线程" 是否被中断,不清除中断标志

二者底层调用同一方法,仅参数不同:interrupted()传入true(清除标志),isInterrupted()传入false(不清除)。

中断的典型使用场景

  • 某个操作超时(如网络请求超过 5 秒需中止);
  • 一组线程中一个出错,需取消其他所有线程;
  • 多线程执行同一任务,只要一个线程成功,其他线程可取消。

四、多线程高频问题:梳理核心概念

最后,整合几个多线程的基础高频知识点,帮你查漏补缺:

1. wait/notify/sleep/yield/join 的区别

方法 作用 是否释放锁 注意事项
wait() 挂起线程,等待唤醒 释放锁 必须在synchronized块中调用
notify() 唤醒对象_WaitSet中的一个线程 不释放锁 需配合wait()使用,唤醒后需重新竞争锁
sleep(long) 让线程暂停指定时间 不释放锁 暂停期间不释放持有的同步锁
yield() 让线程让出 CPU,回到就绪状态 不释放锁 不保证其他线程能获取 CPU,可能立即再次执行
join() 让父线程等待子线程执行完毕 不释放锁 如主线程调用threadA.join(),会等待threadA执行完再继续

join () 的实现原理

主线程调用threadA.join()时,会先获取threadA对象的同步锁,然后进入等待状态;当threadA执行完毕,会调用自身的notifyAll(),唤醒阻塞在threadA锁上的主线程,主线程才能继续执行。

2. 线程上下文切换:为什么会有开销?

线程切换时,CPU 需要保存当前线程的状态(上下文),再加载新线程的状态,整个过程分为 3 步:

  1. 挂起当前线程,将其 CPU 寄存器、程序计数器等状态存入内存;
  2. 从内存中加载新线程的上下文,恢复到 CPU 寄存器;
  3. 跳转到新线程程序计数器指向的代码行,开始执行。

切换的开销来源

  • 直接消耗:CPU 寄存器的保存 / 加载、系统调度器执行、TLB(地址转换缓存)重新加载、CPU 流水线清空;
  • 间接消耗:多核 CPU 中,线程切换可能导致缓存数据失效,需重新从内存读取,影响性能。

3. 为什么要用多线程?

核心目标是提升硬件利用率,降低延迟、提高吞吐量

  • 单线程下,CPU 和 I/O 设备无法并行(如 CPU 计算时,I/O 空闲;I/O 读取时,CPU 空闲),利用率最高 50%;
  • 多线程可让 CPU 和 I/O 并行工作(一个线程计算,一个线程处理 I/O),理论利用率可达 100%;
  • 多核 CPU 下,多线程能充分利用多个核心,大幅提高计算效率;单核下虽有切换开销,但仍能提升 I/O 密集型任务的响应速度。

总结

多线程是并发编程的基础,从进程与线程的关系,到线程的生命周期、中断机制,再到核心方法的区别,每个知识点都需要结合场景理解。这篇笔记梳理了多线程的核心框架,后续可结合实际代码(如线程池的使用、死锁的排查)进一步深化,才能真正掌握多线程的实践技巧。

相关推荐
xyy1233 小时前
.NET SQLite
后端
小蜗牛编程实录3 小时前
6.MySQL 主从复制深度解析:原理、问题与优化方案
后端
xxxcq3 小时前
Go微服务网关开发(3):负载均衡功能的实现
后端·github
uhakadotcom3 小时前
常识:python之中的伪随机数安全风险
后端·面试·github
Goboy4 小时前
【Python修仙笔记.3】Python函数作为秘技 - 封装你的仙法
后端·python
Goboy4 小时前
【Python修仙笔记.4】数据结构法宝 - 存储你的仙器
后端·python
间彧4 小时前
Spring Boot自动配置与"约定大于配置"机制详解
后端
IT_陈寒4 小时前
JavaScript引擎优化:5个90%开发者都不知道的V8隐藏性能技巧
前端·人工智能·后端
JaguarJack5 小时前
PHP "真异步" TrueAsync SAPI 与 NGINX Unit 集成
后端·php