当前位置: 首页 > news >正文

Java 高并发多线程 “基础”面试清单(含超通俗生活案例与深度理解)

一、并行与并发的核心区别是什么?如何通过日常场景快速区分?

• 核心定义差异:并行是“同一时刻有多个任务真正同时执行”,必须依赖多CPU或多核心硬件支持;并发是“同一时刻仅一个任务执行,但通过CPU快速切换任务,让多个任务在一段时间内呈现‘同时进行’的效果”,本质是“时间片轮转”的模拟机制。

• 生活案例(并行):周末家庭大扫除,家里有两个吸尘器(相当于多CPU)。你用一个吸尘器吸客厅地毯(任务1),家人用另一个吸尘器吸卧室地板(任务2)——从下午2点到2点半,两个吸尘器同时工作,客厅和卧室的清洁任务各自推进,没有互相等待,这就是并行,是“真同时”执行。

• 生活案例(并发):还是家庭大扫除,但只有一个吸尘器。你先吸5分钟客厅(任务1),然后关掉吸尘器去整理阳台杂物(CPU切换任务),整理10分钟后,再回来吸剩下的客厅区域——同一时刻只做一件事,但1小时内既完成了客厅吸尘,又整理了阳台,从结果上看像“同时做完两件事”,这就是并发,是“伪同时”。

• 关键区分点:看是否有“多个独立执行载体”(如多吸尘器、多CPU):有就是并行,无就是并发;并行不依赖任务切换,并发完全靠切换实现。

二、进程和线程分别是什么?两者的关系与区别如何通过日常场景理解?

• 进程的定义与类比:进程是“操作系统分配资源的基本单位”,指一个程序运行时的状态,会占用内存、存储、网络等资源。比如打开手机微信就是一个进程——它会占用手机运行内存(加载微信代码和聊天记录)、存储(缓存表情包和图片)、网络(同步消息),这些资源都由“微信进程”统一管理。

• 线程的定义与类比:线程是“操作系统调度CPU的基本单位”,是进程的“执行分支”,负责具体的操作逻辑。比如打开微信后,你同时“刷朋友圈”“发语音消息”“接收文件”——这三个操作就是三个独立线程。真正让CPU“忙碌”的是线程:刷朋友圈时,CPU要加载图片和文字;发语音时,CPU要处理音频编码;这些具体执行步骤,都是线程在占用CPU完成。

• 进程与线程的核心关系:

1. 依附关系:线程不能脱离进程存在,一个进程至少有一个“主线程”(如微信启动时的“界面加载线程”),也可有多“子线程”(如刷朋友圈、发消息的线程)。

2. 资源共享关系:多个线程共享所属进程的资源。比如微信的“刷朋友圈”和“发消息”线程,都能访问同一个“用户账号信息”(存在进程内存中),不用各自重新加载;但每个线程的执行互不干扰——刷朋友圈时,发消息线程不会被打断。

• 进程与线程的关键区别:

◦ 资源分配:进程是“资源所有者”,操作系统把内存、存储分给进程;线程不直接分配资源,只共享进程资源。

◦ 稳定性:一个进程崩溃,旗下所有线程都会跟着终止(如微信闪退,刷朋友圈、发消息线程都停了);但一个线程崩溃,其他线程可能正常运行(如接收文件线程出错,刷朋友圈线程还能继续用)。

三、Java中创建线程的3种方式,各有什么特点?适合哪些日常场景?

Java创建线程的核心是“定义任务逻辑+启动执行”,三种方式的差异集中在“任务定义形式”和“是否能获取执行结果”,具体如下:

1. 继承Thread类

• 实现逻辑:直接继承Thread类,重写run()方法(在run()中定义线程要执行的任务),创建子类对象后调用start()方法启动线程。

• 特点:简单直接,相当于“子承父业”——Thread类已封装“启动线程”的底层能力(start()方法),子类只需专注“具体做什么”,不用关心线程启动的细节。

• 适用场景:适合“简单、独立且无需复用的任务”。比如开发一个系统监控工具,需要单独开线程,每隔10秒打印一次CPU使用率(任务逻辑简单,且只有这一个地方需要该功能)。你可以写一个“CpuMonitorThread”继承Thread,在run()里写“循环获取CPU使用率+暂停10秒”,调用start()启动即可,不用考虑其他线程是否需要复用这个逻辑。

• 局限性:Java是单继承,若子类已继承其他类(如“Student”类继承“Person”类),就不能再继承Thread,灵活性较低。

2. 实现Runnable接口

• 实现逻辑:先定义类实现Runnable接口,重写run()方法(定义任务逻辑),创建该类对象并传给Thread构造方法(让Thread作为“执行者”),最后调用Thread的start()启动。

• 特点:解耦“任务逻辑”和“线程启动”,相当于“分工合作”——Runnable只负责“定义要干的活”,Thread负责“怎么启动线程干活”;且Java支持多实现,一个类可同时实现Runnable和其他接口(如“DataAnalyzer”类既实现Runnable,又实现“Serializable”接口),灵活性高。

• 适用场景:适合“多线程共享同一任务逻辑”的场景。比如开发一个数据统计工具,需要3个线程同时处理一个2000行的Excel表格(每个线程处理600多行),任务逻辑都是“读取行数据+统计销售额”。你可以写一个“SalesCountRunnable”实现Runnable,在run()里写统计逻辑;然后创建3个Thread对象,都传入同一个“SalesCountRunnable”实例,分别启动——3个线程按同一套逻辑处理不同表格片段,不用重复写3遍统计代码。

3. 实现Callable接口

• 实现逻辑:定义类实现Callable接口(泛型指定返回值类型),重写call()方法(定义任务逻辑且能返回结果);创建Callable对象并传给FutureTask(负责“接收和存储结果”);将FutureTask传给Thread构造方法,调用start()启动;最后用FutureTask的get()方法获取call()的返回结果。

• 特点:能获取任务执行结果,相当于“干活带反馈”——前两种方式的run()是void类型,线程执行完无法返回结果(如线程查询快递进度,没法把“已送达”的结果传回来);而Callable的call()有返回值,配合FutureTask能拿到结果,还能处理执行中的异常(如查询快递时网络出错,get()会抛出异常)。

• 适用场景:适合“需要获取任务结果的场景”。比如开发一个外卖APP,需要开线程查询“用户当前订单的预计送达时间”——这个任务必须返回结果(如“30分钟后送达”),否则APP无法给用户展示。你可以写一个“DeliveryTimeCallable”实现Callable,在call()里调用外卖平台API查询时间,返回“预计送达时间”;通过FutureTask的get()拿到结果后,再显示到APP订单页面。

• 注意点:get()是“阻塞式”的——调用后当前线程会等待,直到call()执行完返回结果才继续(如APP主线程调用get()后,会等查询完再显示时间,避免显示“加载中”卡顿),实际开发中可配合超时设置(get(long timeout, TimeUnit unit)),避免长期阻塞。

四、为什么启动线程必须用start()?直接调用run()为什么不行?

• 核心原因:start()会“让JVM创建新线程”,新线程独立执行run()方法;直接调用run()只是“在当前线程中执行一个普通方法”,没有新线程产生,完全没有多线程效果。

• start()的底层逻辑:调用thread.start()时,JVM会做两件关键事:1. 在操作系统中创建新线程(分配CPU调度权、线程栈等资源);2. 让新线程去执行thread的run()方法。此时新线程与调用start()的“主线程”独立——比如主线程在浏览网页,新线程在下载文件,两者互不干扰,CPU会轮流调度。

• 直接调用run()的问题:run()本质是普通成员方法,没有“创建新线程”的逻辑。若直接调用thread.run(),相当于“主线程暂停自己的工作,去执行run()里的代码”——比如主线程在浏览网页,调用run()后会暂停浏览,先执行“下载文件”的代码,等下载完才继续浏览,是“顺序执行”,没有多线程的“并行/并发”效果。

• 生活案例(start()的正确用法):你(主线程)让家人(新线程)帮忙去楼下买水果(run()的任务)。你喊一声“你去买水果吧”(调用start()),家人听到后自己出门(JVM创建新线程),你继续在家看书(主线程执行自己的逻辑)——这是多线程,你和家人同时做不同的事。

• 生活案例(直接调用run()的错误用法):你让家人买水果,但不是让他自己去,而是拉着他的手,让他在你面前“模拟去超市买水果的动作”(直接调用run())——家人没真出门,你也没法看书,只能盯着他模拟动作,等“模拟完买水果”才继续看书,是顺序执行,没有多线程意义。

五、线程常用的调度方法有哪些?各自作用是什么?

线程调度方法是“控制线程执行顺序、暂停/唤醒”的核心工具,主要来自Object类(wait()/notify()/notifyAll())和Thread类(join()/sleep()/yield()/interrupt()),具体如下:

1. Object类的等待-唤醒方法:wait()/notify()/notifyAll()

• 核心作用:实现“线程间协作”——让一个线程等待某个条件(如“饭菜做好”),另一个线程满足条件后唤醒它,避免线程“空等”(如一直问“做好了吗”,浪费CPU资源)。

• 注意点:这三个方法必须在“同步代码块或同步方法”中使用(需先获取共享对象的锁),否则会抛出IllegalMonitorStateException异常。

(1)wait()方法

• 作用:让当前线程“释放锁,进入等待状态”,直到被其他线程唤醒或中断,才重新竞争锁继续执行。

• 场景:一家人等爸爸做晚饭。爸爸把菜放进锅里煮(获取“饭菜”的锁),然后让妈妈和孩子“等饭菜煮好再吃”(妈妈和孩子的线程调用wait())——此时妈妈和孩子会释放“饭菜”的锁,去客厅看电视(进入等待状态,不占用CPU),不会一直守在厨房问“好了吗”。

(2)notify()方法

• 作用:唤醒“在当前共享对象上等待的一个随机线程”,让它重新竞争锁;若多个线程等待,具体唤醒哪个是随机的。

• 场景:饭菜煮好后,爸爸喊一声“饭好了,谁来端菜”(调用notify())——妈妈和孩子中会有一个被随机唤醒(比如妈妈),妈妈去厨房竞争“饭菜”的锁,拿到锁后端菜;孩子还在等待,需要爸爸再喊一次(再调用notify())才会被唤醒。

(3)notifyAll()方法

• 作用:唤醒“在当前共享对象上等待的所有线程”,让它们一起竞争锁;最终只有一个线程能拿到锁,其他线程回到阻塞状态。

• 场景:爸爸煮好饭后喊“饭好了,大家都来吃”(调用notifyAll())——妈妈和孩子都被唤醒,一起去厨房竞争“饭菜”的锁(比如孩子跑得快先拿到锁),孩子盛饭时妈妈阻塞(等孩子释放锁),孩子盛完后妈妈才能盛饭。

2. Thread类的join()方法

• 作用:让“当前线程”等待“目标线程”执行完毕后再继续。比如线程A调用线程B的join(),线程A会暂停,直到线程B执行完(状态变为TERMINATED),线程A才继续。

• 场景:你(主线程)要写生日派对计划,需要朋友(线程B)帮忙统计“能来参加的人数”(线程B的任务)。你让朋友开始统计(启动线程B),然后调用threadB.join()——此时你会暂停写计划,等朋友统计完(告诉你“10人能来”),拿到数据后才继续写“准备多少份蛋糕”,避免计划缺关键数据。

• 重载方法:join(long millis)——设置“最大等待时间”,如threadB.join(60000)(等待1分钟):1分钟内线程B执行完,你继续写计划;1分钟后没执行完,你就先写其他部分(如场地布置),等朋友统计完再补人数相关内容。

3. Thread类的sleep(long millis)方法

• 作用:让“当前线程”暂时让出CPU,进入“TIME_WAITING(超时等待)”状态,持续指定时间(millis毫秒);时间到后回到“RUNNABLE(就绪)”状态,等待CPU调度;期间不释放持有的“锁资源”(如synchronized锁)。

• 场景:你(当前线程)在用手机看电视剧,设置“每看30分钟,暂停2分钟活动眼睛”(调用Thread.sleep(120000))——此时你会暂停看剧(让出CPU),进入活动状态(超时等待);2分钟后回到“准备看剧”状态(就绪),等手机CPU调度(无其他APP抢资源就继续看);且活动期间你不会把手机让给别人(不释放锁),别人想拿你手机看剧,只能等你活动完。

• 关键区别(与wait()对比):sleep()不释放锁,wait()释放锁;sleep()是Thread静态方法,只能让当前线程休眠;wait()是Object方法,能让当前线程释放锁并等待。

4. Thread类的yield()方法

• 作用:让当前线程向CPU调度器“暗示”:“我现在不急着执行,你可以先调度其他优先级相同或更高的线程”;但调度器可“忽略暗示”,继续让当前线程执行——yield()只是“建议”,非“强制”。

• 场景:你(当前线程)在用平板玩拼图游戏,此时平板弹出“系统更新需要后台下载”(另一个线程需CPU)。你调用yield(),暗示平板CPU:“我先暂停拼图,你先处理更新下载吧”——若调度器接受暗示,会暂停拼图去下载更新;若调度器觉得“拼图优先级更高”(如你快拼完了),会忽略暗示,让你继续玩。

• 特点:yield()后线程回到“就绪状态”,和其他线程一起竞争CPU;只给“优先级相同或更高”的线程机会,不给优先级更低的线程。

5. Thread类的interrupt()方法(线程中断)

• 核心逻辑:Java线程中断不是“强制终止线程”,而是“给线程发‘中断信号’(设置中断标志位)”,线程需自己检测信号,决定是否停止——本质是“线程间协作”,非“强制命令”。

• 相关方法:

1. interrupt():给目标线程设“中断标志位”(true),不停止线程执行;

2. isInterrupted():查询目标线程的中断标志位(返回true/false),不清除标志;

3. Thread.interrupted():查询“当前线程”的中断标志位,会清除标志(查询后从true变false)。

• 场景:你(主线程)让同事(线程A)帮忙去咖啡店买拿铁(线程A的任务)。同事刚出门,你想喝美式了,发消息说“别买拿铁,买美式”(调用threadA.interrupt(),设中断标志)——同事收到消息(在run()里检测isInterrupted()),会停止去买拿铁,转而买美式(自己处理中断);若同事没看消息(没检测标志),会继续买拿铁,不会被强制停止。

• 注意点:若线程处于“WAITING/TIME_WAITING”状态(如调用wait()/sleep()/join()),调用interrupt()会让线程抛出InterruptedException,且清除中断标志(如同事在等咖啡制作时(wait()状态)收到消息,会立刻停止等待,抛异常后去买美式)。

六、Java线程有哪六种状态?各状态的触发场景是什么?

Java线程状态定义在Thread.State枚举中,共六种,覆盖“创建-执行-结束”的全生命周期,具体如下:

1. NEW(初始状态)

• 定义:线程对象已创建,但未调用start(),JVM未分配CPU、线程栈等资源。

• 场景:你在手机上点击“淘宝APP”图标——此时APP的“主线程对象”已创建(相当于“有了要干活的人”),但APP还在加载启动页(未调用start()),这个主线程没占用CPU,也没执行代码,就是NEW状态。

• 切换方向:调用start()后,切换到RUNNABLE状态(如APP加载完启动页,主线程开始加载首页商品)。

2. RUNNABLE(运行状态)

• 定义:合并操作系统的“就绪”和“运行中”状态——线程要么正在占用CPU执行(运行中),要么已准备好执行,等待CPU调度(就绪)。

• 场景:淘宝APP加载完首页后,你“刷商品列表”(主线程任务)——若手机CPU空闲,主线程占用CPU加载并显示商品(运行中);若手机同时在后台上传照片(另一个线程占CPU),主线程会等CPU空闲(就绪),等上传线程让出CPU后再继续刷商品。

• 切换方向:

◦ 时间片用完,从“运行中”回到“就绪”(仍为RUNNABLE);

◦ 调用sleep()/wait()/join()或竞争锁失败,切换到BLOCKED/WAITING/TIME_WAITING;

◦ 执行完run(),切换到TERMINATED。

3. BLOCKED(阻塞状态)

• 定义:线程因“竞争锁失败”被阻塞,需等其他线程释放锁后重新竞争,才能回到RUNNABLE——仅与“锁竞争”相关(如synchronized锁)。

• 场景:淘宝APP中,你同时操作“修改收货地址”和“提交订单”(两个线程)。这两个操作都需访问“收货地址数据”(共享资源,synchronized加锁)——若“修改地址”线程先拿锁,开始编辑地址,“提交订单”线程竞争锁失败,进入BLOCKED状态;直到“修改地址”线程释放锁(编辑完),“提交订单”线程才重新竞争锁,回到RUNNABLE。

4. WAITING(等待状态)

• 定义:线程因调用“无超时的wait()/join()”或LockSupport.park(),进入“无限期等待”——必须等其他线程唤醒(如notify()),才能回到RUNNABLE。

• 场景:你在淘宝APP上抢购限量商品,点击“提交订单”后,进入“等库存锁定”页面(主线程调用wait(),进入WAITING)——此时主线程一直等,直到系统锁定库存(另一个线程调用notify()),才被唤醒,显示“订单提交成功”;若没唤醒,会一直等。

5. TIME_WAITING(超时等待状态)

• 定义:线程因调用“有超时的方法”(如Thread.sleep(long)、Object.wait(long)),进入“有限期等待”——要么被唤醒,要么超时后自动回到RUNNABLE,不用一直等。

• 场景:你在淘宝APP上设置“30分钟内未付款自动取消订单”(主线程调用wait(30601000),进入TIME_WAITING)——两种结果:1. 20分钟内你付款(其他线程调用notify()),主线程被唤醒,取消“自动取消”;2. 30分钟到了没付款,主线程超时唤醒,执行“取消订单”逻辑。

• 关键区别(与WAITING对比):TIME_WAITING有“超时时间”,到点自动唤醒;WAITING无超时,必须等唤醒。

6. TERMINATED(终止状态)

• 定义:线程的run()执行完毕,或因未捕获异常终止,进入最终状态——一旦进入,无法回到其他状态(如调用start()会抛IllegalThreadStateException)。

• 场景:你在淘宝APP上的订单“确认收货”后,主线程的任务(“跟踪订单状态到完成”)执行完——此时主线程进入TERMINATED状态,不会再更新订单状态;若想查看“售后进度”,需创建新线程,不能用之前的主线程。

• 触发方式:1. 正常终止:run()执行完;2. 异常终止:run()抛未捕获异常(如跟踪订单时网络出错,抛NullPointerException且未处理)。

七、什么是线程上下文切换?为什么会发生?如何理解?

• 核心定义:CPU从一个线程切换到另一个线程执行时,“保存当前线程执行状态,加载新线程执行状态”的过程——本质是“CPU资源交接”,让多线程共享CPU实现并发。

• 为什么会发生:CPU核心数有限(如手机CPU为8核),但运行的线程可能有几十个(如后台有微信、淘宝、抖音的线程)。为让所有线程都有执行机会,CPU给每个线程分配“时间片”(如10毫秒),时间片用完后切换到下一个线程——这个切换过程就是上下文切换。

• 切换核心步骤(类似“交接工作”):

1. 保存当前线程“上下文信息”:包括程序计数器(执行到哪行代码)、寄存器值(CPU临时数据)、线程栈(局部变量、方法调用栈)——相当于“当前线程记下来干到哪一步”。

2. 加载新线程“上下文信息”:从内存读取新线程之前保存的信息,恢复到CPU——相当于“新线程接着上次的进度继续干”。

3. 切换CPU执行权:让CPU执行新线程代码,新线程进入“运行中”,当前线程进入“就绪”。

• 生活案例:老师(CPU)批改4个学生的语文试卷(4个线程),每个学生的试卷批8分钟(时间片)。

1. 老师先批学生A的试卷,批到第4题时8分钟到(时间片用完)——老师在试卷上画标记,记“批到第4题”(保存A的上下文)。

2. 老师拿起学生B的试卷,找到上次批到的第2题(加载B的上下文),从第2题继续批(切换执行权,B进入运行中)。

3. 8分钟后,老师切换回学生A的试卷,从第4题继续批——这个“记A进度→批B→再批A”的过程,就是上下文切换。

• 切换代价:切换需消耗CPU时间(保存/加载信息),占用内存(存储上下文)——若线程太多,切换频繁,CPU会把大部分时间用在“切换”上,而非“批试卷”(执行任务),导致系统卡顿(如手机反应慢)。

八、什么是守护线程?它和用户线程的核心区别是什么?

• 守护线程的定义:守护线程(Daemon Thread)是“服务用户线程的线程”,作用是辅助用户线程执行,如JVM的垃圾回收线程、日志打印线程——相当于“奶茶店店员”,用户线程(“顾客”)在,店员就在;顾客走了,店员也下班。

• 用户线程的定义:用户线程(User Thread)是“执行核心业务的线程”,如淘宝APP的“刷商品”线程、微信的“发消息”线程——相当于“顾客”,是系统运行的核心,只要有一个顾客在,系统就不关门。

• 核心区别:

1. 生命周期依赖:JVM退出只看用户线程——只要有一个用户线程运行,JVM不退出,守护线程继续工作;最后一个用户线程终止,不管守护线程是否执行完,JVM都会强制终止所有守护线程,然后退出。

2. 创建方式:创建线程后,调用thread.setDaemon(true)(需在start()前设置,否则抛异常);默认创建的线程是用户线程(setDaemon(false))。

3. 任务性质:守护线程执行“后台辅助任务”(如清理内存、打印日志);用户线程执行“核心业务”(如处理用户请求、计算数据)。

• 生活案例:你(用户线程)在奶茶店喝奶茶(执行核心任务),店员(守护线程)帮你加珍珠、收杯子(辅助任务)。

◦ 只要你还在店里喝(用户线程运行),店员就一直服务(守护线程运行)。

◦ 你喝完离开(最后一个用户线程终止),不管店员有没有收拾完桌子(守护线程没执行完),店员都会停止服务,奶茶店关门(JVM退出)。

• 开发中的守护线程例子:

1. 垃圾回收线程(GC):用户线程创建对象(占内存),GC线程后台回收无用对象(释放内存);所有用户线程终止,无新对象创建,GC线程没用了,JVM终止GC并退出。

2. 系统监控线程:开发APP时,开守护线程每隔15秒监控CPU使用率——APP的用户线程(如刷页面)运行时,监控线程持续收集数据;APP退出(用户线程终止),监控线程被强制终止。

九、线程间有哪些通信方式?各适合什么场景?

线程间通信是“多线程交换数据、协调执行”的机制,核心是“让线程感知其他线程的状态或数据变化”,常见5种方式如下:

1. volatile关键字

• 通信原理:volatile修饰的变量保证“可见性”——一个线程修改变量后,其他线程能立刻看到最新值(避免从“工作内存”读旧值,直接从“主内存”读最新值);但不保证“原子性”(如i++非原子)和“有序性”(除自身读写顺序)。

• 特点:轻量级,无需加锁,性能比synchronized好;功能有限,仅保证可见性,不能解决“多线程修改冲突”。

• 适用场景:适合“一个线程修改,多个线程读取”且修改是原子的场景。比如开发“闹钟APP”,一个线程(计时线程)每隔1秒把“剩余时间”(volatile int time)减1,多个线程(界面线程、铃声线程)读time的值——计时线程修改后,界面线程立刻显示最新剩余时间,铃声线程判断time是否为0(触发闹钟),且time的修改(time--)是原子的。

• 场景理解:家里的电子钟(主内存)显示倒计时“10分钟”,你(线程A)把时间改成“9分钟”(修改volatile变量),家人(线程B/C)看电子钟时,能立刻看到“9分钟”(可见性)——但两人同时改时间,可能出现显示错误(不保证原子性)。

2. synchronized关键字

• 通信原理:synchronized通过“加锁”实现通信,有两个核心作用:1. 互斥性:同一时刻仅一个线程进入同步代码块,保证“排他性访问共享变量”;2. 可见性:线程退出同步块时,把修改后的变量刷新到主内存;进入同步块时,从主内存读最新值——相当于“线程通过主内存交换数据,且仅一人操作主内存”。

• 特点:功能全面,保证可见性和原子性,但性能比volatile差(加锁/解锁耗资源)。

• 适用场景:适合“多线程同时修改共享变量”,需保证数据一致的场景。比如开发“秒杀APP”,100个用户抢5件商品(共享变量int stock=5)——每个用户的抢购操作(stock--)用synchronized加锁:同一时刻仅一个用户执行stock--,执行完刷新到主内存;其他用户进入同步块时,看到最新库存(4、3...),避免“超卖”(库存变负)。

• 场景理解:家里冰箱(主内存)有5瓶可乐(stock=5),3个孩子(3个线程)同时想拿——妈妈(synchronized)给冰箱加锁,仅一个孩子能开门拿(互斥性);拿完后告诉妈妈“还剩4瓶”(刷新主内存),下一个孩子开门时看到最新数量(可见性),不会出现“3个孩子拿完还剩5瓶”的错误。

3. 管道输入/输出流(PipedStream)

• 通信原理:管道流是“线程间直接传数据的流”,基于内存实现(不写磁盘),分字节流(PipedInputStream/PipedOutputStream)和字符流(PipedReader/PipedWriter)——一个线程“写数据到管道”(输出端),另一个线程“从管道读数据”(输入端),数据直接传递,不用中间存储。

• 特点:传输快(基于内存),但仅用于“两个线程通信”,且需把输入流和输出流“连接”(connect()),否则抛异常。

• 适用场景:适合“两线程频繁、快速传数据”的场景,如“生产者-消费者”模型。比如开发“实时语音转文字工具”,一个线程(录音线程)读取用户语音(数据),通过PipedOutputStream写入管道;另一个线程(转文字线程)通过PipedInputStream从管道读语音数据,转成文字后输出——数据不用存文件,直接传递,转文字速度快。

• 场景理解:两个同事(两线程)相邻工位传文档(数据)——同事A(输出端)把文档内容念给同事B(输入端)听(内存传递),不用U盘(磁盘)或微信(中间存储),直接传递效率高。

4. Thread.join()方法

• 通信原理:join()通过“当前线程等目标线程执行完”,实现“当前线程获取目标线程的结果”——目标线程执行完后,把结果存到共享变量(如成员变量),当前线程等待结束后读共享变量拿结果,本质是“共享变量+等待机制”。

• 特点:简单直接,适合“当前线程依赖目标线程结果”的场景,但会阻塞当前线程。

• 适用场景:适合“任务有依赖”的场景。比如开发“旅游APP”,需先查“目的地天气”(线程A),再推荐“出行装备”(主线程)——主线程启动线程A后,调用threadA.join(),等线程A查到“雨天”(存到共享变量);主线程等待结束后,读“雨天”结果,推荐“雨伞、雨衣”,避免装备与天气不匹配。

• 场景理解:你(主线程)要出门,让妈妈(线程A)查是否下雨(目标任务)——你等妈妈查完(join()),听到“下雨了”(拿结果),才拿雨伞出门(执行后续逻辑),不会没查天气就乱拿装备。

5. ThreadLocal类

• 通信原理:ThreadLocal是“线程本地变量”,为每个线程创建“独立变量副本”——线程通过set()设值,get()拿自己的副本值,不同线程的副本互不干扰,本质是“通过‘线程独有副本’避免共享竞争”,间接实现“隔离式通信”(无需交换数据,各自用自己的数据)。

• 特点:线程隔离,无需加锁,性能好;但变量副本存在线程中,线程销毁后副本也销毁,不能用于“线程间共享数据”。

• 适用场景:适合“每个线程需独立变量”的场景,如用户会话管理。比如开发“电商后台”,每个用户请求对应一个线程(用户A、B的请求线程)——用ThreadLocal存“用户登录信息”(用户名、权限):用户A的线程set()自己的信息,用户B的线程set()自己的信息;两线程通过get()仅能拿到自己的信息,不会拿错(避免权限混乱);请求处理完,线程销毁,登录信息副本也回收。

• 场景理解:公司每个员工(每个线程)有自己的工牌(ThreadLocal副本),员工刷工牌(get())仅能打开自己的工位门(访问自己的变量),不能打开别人的门(看不到其他线程的变量);员工离职(线程销毁),工牌也回收(副本销毁)。

 

http://www.dtcms.com/a/438529.html

相关文章:

  • 什么网站可以做兼职销售群晖可以做网站服务器吗
  • 【LaTeX】 8 LaTeX 表格制作
  • 日常踩雷系列-vscode无法输入中文
  • 巩义网站建设指标点历史文化街区和历史建筑信息平台
  • 打工人日报#20251003
  • Java学习之旅第一季-27:输入与输出
  • 地方网站域名选择嘉伟网络智能建站
  • Leetcode 3695. Maximize Alternating Sum Using Swaps
  • 模型瘦身四剑客:剪枝、量化、低秩分解、知识蒸馏详解
  • php 校园网站设计如何做网站流程图
  • 如何高效下载 YouTube 视频?实用工具推荐
  • 【洛谷】6 道题吃透堆的应用:模板堆、第 k 小、最小函数值等全攻略
  • MySQL库的操作(ubuntu)
  • 利用AI赋能抑郁症管理:创业项目与技术方案
  • 中国网站建设市场分析报告上海网站设计合理柚v米科技
  • 【IMX6ULL项目复现】sg90电机-pwm
  • 2025软件供应链安全实战:从漏洞修补到风险预测的转型指南
  • iOS26 打开开发者模式
  • 注销主体备案与网站备案沧州最火网站
  • AI智能体在研究分析中的仿真应用:AI驱动的复杂系统建模与“理论压缩”
  • 表格识别技术:突破传统OCR的局限,通过定位-解析-重建三步策略攻克无边框、合并单元格等视觉难题
  • 免费企业模板网站制作网页设计公司
  • 开放定址法的平均查找长度计算
  • MEVless,MEV解决之道
  • 网站 自适应文字大小怎么做响应式网站建设公司
  • 手写MyBatis第89弹:动态SQL解析与执行时机深度剖析
  • 解读172页“十五五”企业战略规划指导手册【附全文阅读】
  • 网站开发个人工作室网站推送怎么做的
  • 机器学习:逻辑回归
  • 机器学习——SVM支持向量机详解