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

Java多线程基础

Java多线程基础

作者:没有四次元口袋的蓝胖
日期:2026-06-17
标签:Java, 多线程


一、线程与进程

进程:操作系统分配资源的基本单位,每个正在运行的程序就是一个进程。
线程:CPU调度的基本单位,一个进程内可以有多个线程,它们共享进程的内存资源。

对比项进程线程
资源独立内存空间共享进程内存
开销创建/切换成本高创建/切换成本低
通信需要IPC机制直接共享变量(需同步)
崩溃影响互不影响一个线程崩可能拖垮整个进程

面试坑点:面试官问"进程和线程的区别",别只背概念——要提到线程共享内存带来的并发安全问题,这才是后续synchronized、Lock等知识的基础。


二、创建线程的三种方式

2.1 继承Thread类

classMyThreadextendsThread{@Overridepublicvoidrun(){System.out.println("线程运行:"+Thread.currentThread().getName());}}// 使用MyThreadt=newMyThread();t.start();// 注意是start(),不是run()!

要点

  • 继承Thread类,重写run()方法
  • 调用start()启动线程,JVM会自动调用run()
  • Java单继承,已经继承了其他类就不能再用这种方式

2.2 实现Runnable接口

classMyRunnableimplementsRunnable{@Overridepublicvoidrun(){System.out.println("线程运行:"+Thread.currentThread().getName());}}// 使用方式1:传入Thread构造器Threadt=newThread(newMyRunnable());t.start();// 使用方式2:Lambda简化(Runnable是函数式接口)newThread(()->{System.out.println("Lambda线程运行");}).start();

要点

  • 实现Runnable接口,实现run()方法
  • 避免了单继承限制,更灵活
  • 可以用Lambda表达式简化写法
  • 推荐方式,也是实际开发中最常用的

2.3 实现Callable接口

classMyCallableimplementsCallable<String>{@OverridepublicStringcall()throwsException{return"Callable返回结果";}}// 使用:配合FutureTaskFutureTask<String>futureTask=newFutureTask<>(newMyCallable());Threadt=newThread(futureTask);t.start();// 获取返回值(会阻塞直到线程执行完)Stringresult=futureTask.get();System.out.println(result);

要点

  • 实现Callable<V>接口,重写call()方法
  • call()可以有返回值,可以抛出异常
  • 必须配合FutureTask使用(FutureTask同时实现了Runnable和Future)
  • get()方法会阻塞,一般放在最后调用

三种方式对比

对比项继承Thread实现Runnable实现Callable
返回值有(泛型)
异常不能抛出checked异常不能抛出checked异常可以抛出
继承限制单继承
复用性差(每次new一个Thread)好(同一个Runnable可传给多个Thread)
复杂度最简单简单稍复杂
推荐度⭐⭐⭐⭐⭐(需要返回值时用)

面试高频题:三种创建线程的方式有什么区别?

  • 回答要点:继承限制返回值异常处理实际开发中推荐Runnable(因为线程池接收的就是Runnable/Callable)
  • 加分项:提到线程池才是生产环境的标准用法,三种方式只是基础

面试坑点:调用run()start()有什么区别?

  • start():启动新线程,JVM调用run()
  • run():只是普通方法调用,在当前线程执行,不会创建新线程
  • 同一个Thread对象,start()只能调用一次,重复调用会抛IllegalThreadStateException

三、线程常见API

3.1 获取和设置线程名称

Threadt=newThread(()->{},"我的线程");// 构造时命名t.setName("线程A");// 设置名称Stringname=t.getName();// 获取名称StringmainName=Thread.currentThread().getName();// 当前线程名称

线程命名规则

  • main线程叫main
  • 默认线程名:Thread-0Thread-1Thread-2
  • 建议给线程起有意义的名字,方便排查问题

3.2 线程休眠

Thread.sleep(1000);// 休眠1000毫秒(1秒)

要点

  • sleep()是静态方法,让当前线程休眠
  • 休眠期间不释放锁(这是和wait()的重要区别)
  • 时间到后进入就绪态,不一定是马上执行
  • InterruptedException必须处理

3.3 线程让步

Thread.yield();// 礼让线程

要点

  • 静态方法,提示线程调度器当前线程愿意让出CPU
  • 只是建议,不保证生效
  • 实际开发中很少用

3.4 线程优先级

t.setPriority(Thread.MAX_PRIORITY);// 10t.setPriority(Thread.MIN_PRIORITY);// 1t.setPriority(Thread.NORM_PRIORITY);// 5(默认)

要点

  • 优先级范围1-10,默认5
  • 优先级只是建议,不保证高优先级一定先执行
  • 不同操作系统的调度策略不同,不要依赖优先级

面试坑点sleep()wait()的区别?

  • sleep()是Thread的静态方法,wait()是Object的实例方法
  • sleep()不释放锁,wait()释放锁
  • sleep()到时间自动唤醒,wait()需要notify()/notifyAll()唤醒
  • sleep()可以在任何地方调用,wait()必须在同步块中调用

四、守护线程

4.1 什么是守护线程

守护线程(Daemon Thread):为其他线程提供服务的后台线程,当所有非守护线程结束后,JVM会退出,守护线程也会随之销毁。

4.2 使用方式

Threaddaemon=newThread(()->{while(true){System.out.println("守护线程运行中...");try{Thread.sleep(500);}catch(InterruptedExceptione){}}});daemon.setDaemon(true);// 必须在start()之前设置!daemon.start();

4.3 典型应用

  • GC线程:JVM的垃圾回收器就是守护线程
  • 监控线程:后台监控、日志采集
  • 心跳线程:保持连接活跃

4.4 注意事项

  • setDaemon(true)必须在start()之前调用,否则抛IllegalThreadStateException
  • 守护线程中finally不一定执行(JVM退出时守护线程直接终止)
  • 守护线程创建的子线程默认也是守护线程

面试坑点:守护线程的finally块一定会执行吗?

  • 不一定!当所有用户线程结束,JVM退出时守护线程直接被终止,finally可能来不及执行
  • 所以不要在守护线程中做需要保证完成的操作(如关闭资源、写文件等)

五、join的使用

5.1 join是什么

join():等待调用该方法的线程执行完毕,再继续执行当前线程。简单说就是线程插队

5.2 使用方式

Threadt1=newThread(()->{System.out.println("t1执行...");});Threadt2=newThread(()->{try{t1.join();// t2等t1执行完再继续}catch(InterruptedExceptione){}System.out.println("t2执行...");});t1.start();t2.start();// 输出:t1执行... → t2执行...

5.3 join的重载方法

方法说明
join()等待线程执行完毕
join(long millis)最多等待millis毫秒
join(long millis, int nanos)最多等待millis毫秒+nanos纳秒

5.4 典型场景

// 场景:主线程等待所有子线程完成后再汇总结果Threadt1=newThread(task1);Threadt2=newThread(task2);t1.start();t2.start();t1.join();// 等t1执行完t2.join();// 等t2执行完System.out.println("所有任务完成,开始汇总");

面试坑点join()的底层实现是什么?

  • 底层用的是wait(),在join的线程存活期间不断wait(0)
  • 当被join的线程执行完毕(即isAlive()返回false),自动notifyAll()
  • 所以join()会释放锁(因为底层是wait()

面试坑点join()sleep()的区别?

  • join()等待另一个线程结束,sleep()只让当前线程休眠固定时间
  • join()底层是wait(),会释放锁;sleep()不释放锁
  • join()是实例方法,sleep()是静态方法

六、interrupt的使用

6.1 为什么需要interrupt

Java没有安全的方式强制停止线程(stop()已废弃,会导致数据不一致),所以采用协作式中断:一个线程请求另一个线程中断,由被请求的线程自行决定如何响应。

6.2 三个核心方法

方法说明
interrupt()中断线程(设置中断标志位为true)
isInterrupted()判断线程是否被中断(不清除标志位)
static interrupted()判断当前线程是否被中断(清除标志位

6.3 使用场景

场景1:打断正在运行的线程

Threadt=newThread(()->{while(!Thread.currentThread().isInterrupted()){// 正常执行任务System.out.println("工作中...");}System.out.println("收到中断信号,优雅退出");});t.start();// 2秒后请求中断Thread.sleep(2000);t.interrupt();

场景2:打断处于阻塞状态的线程(sleep/wait/join)

Threadt=newThread(()->{try{Thread.sleep(10000);// 休眠10秒}catch(InterruptedExceptione){// sleep/wait/join被interrupt时会抛InterruptedException// 同时清除中断标志位!System.out.println("被中断了,中断标志位:"+Thread.currentThread().isInterrupted());// 输出:被中断了,中断标志位:false}});t.start();Thread.sleep(1000);t.interrupt();

6.4 关键注意事项

  1. interrupt()不会强制停止线程,只是设置标志位
  2. 阻塞中的线程被中断会抛InterruptedException,同时清除标志位
  3. 捕获InterruptedException后,如果想保持中断状态,需要再次调用interrupt()
catch(InterruptedExceptione){Thread.currentThread().interrupt();// 重新设置中断标志// 然后决定是退出还是继续}
  1. 不要吞掉InterruptedException——要么重新抛出,要么重新设置中断标志

面试高频题:如何优雅地停止一个线程?

  • 推荐方式:用interrupt()+检查中断标志位
  • 不推荐stop()(已废弃,不安全)、suspend()(已废弃,容易死锁)
  • 其他方式:用volatile boolean标志位(但不能打断阻塞状态)
  • 加分项:对比interruptvolatile标志位的区别——interrupt能打断sleep/wait/join等阻塞,volatile标志位不能

面试坑点isInterrupted()interrupted()的区别?

  • isInterrupted():实例方法,不清除中断标志位
  • interrupted():静态方法,清除中断标志位
  • 调用interrupted()后,再调用isInterrupted()返回false

七、线程的生命周期

7.1 六种状态

Java线程在Thread.State枚举中定义了6种状态

状态说明触发条件
NEW新建new Thread()后,还没调用start()
RUNNABLE可运行调用start()后,包含就绪和运行中
BLOCKED阻塞等待获取synchronized锁
WAITING等待wait()/join()/LockSupport.park(),无限期等待
TIMED_WAITING计时等待sleep(ms)/wait(ms)/join(ms),有时间限制的等待
TERMINATED终止run()方法执行完毕或抛出未捕获异常

7.2 状态转换图

start() NEW ──────────→ RUNNABLE ←─────────────────────┐ │ ↑ │ │ │ 获取到锁 │ │ │ │ ↓ │ │ BLOCKED(等待synchronized锁) │ │ │ │ 获取到锁 │ ↓ │ RUNNABLE │ │ │ │ │ │ wait()/join() │ │ ↓ │ │ WAITING ───── notify()/notifyAll() ──→ RUNNABLE │ │ │ │ │ sleep(ms)/wait(ms)/join(ms) │ ↓ │ │ TIMED_WAITING ── 超时/notify() ──→ RUNNABLE │ │ │ run()执行完毕/异常 │ ↓ │ TERMINMINATED ←──────────────────────┘

7.3 关键转换说明

NEW → RUNNABLE:调用start()

RUNNABLE → BLOCKED:线程尝试进入synchronized代码块/方法,但锁被其他线程持有

RUNNABLE → WAITING

  • Object.wait()(无参)
  • Thread.join()(无参)
  • LockSupport.park()

RUNNABLE → TIMED_WAITING

  • Thread.sleep(ms)
  • Object.wait(ms)
  • Thread.join(ms)
  • LockSupport.parkNanos()

BLOCKED → RUNNABLE:获取到synchronized

WAITING → RUNNABLE

  • notify()/notifyAll()唤醒(从wait()
  • 被等待的线程执行完毕(从join()
  • LockSupport.unpark()

TIMED_WAITING → RUNNABLE:超时自动唤醒,或被notify()等提前唤醒

RUNNABLE → TERMINATEDrun()正常结束或抛出未捕获异常

7.4 常见混淆点

面试坑点1:RUNNABLE包含"就绪"和"运行中"两个状态吗?

  • 是的!Java线程的RUNNABLE对应操作系统线程的就绪(Ready)和运行(Running)
  • 因为线程是否正在使用CPU由操作系统调度,Java层面无法区分
  • 所以Java没有把"就绪"和"运行中"拆成两个状态

面试坑点2:BLOCKED和WAITING的区别?

  • BLOCKED:等待获取synchronized锁,被动阻塞
  • WAITING:主动等待(调用了wait()/join()等),等待某个条件或另一个线程
  • BLOCKED是锁竞争导致,WAITING是主动让出CPU
  • Lock.lock()导致的等待不在BLOCKED状态,而是WAITING(LockSupport.park)

面试坑点3:调用sleep()后线程进入什么状态?

  • TIMED_WAITING,不是BLOCKED
  • sleep不释放锁,但线程本身处于计时等待状态
  • 很多人会误答RUNNABLE或BLOCKED

八、思维导图速览

Java多线程基础 ├── 线程 vs 进程 │ ├── 进程:资源分配单位,独立内存 │ ├── 线程:CPU调度单位,共享内存 │ └── 核心:共享内存→并发安全问题 │ ├── 创建线程三种方式 │ ├── 继承Thread:简单,单继承限制 │ ├── 实现Runnable:推荐,无继承限制,可Lambda │ └── 实现Callable:有返回值,配FutureTask │ └── ⚡ start() vs run():start启动新线程,run普通调用 │ ├── 常见API │ ├── getName/setName:线程命名 │ ├── sleep(ms):休眠,不释放锁 │ ├── yield():让步,只是建议 │ └── setPriority():优先级,不保证 │ ├── 守护线程 │ ├── setDaemon(true):start()前设置 │ ├── 所有用户线程结束→JVM退出→守护线程销毁 │ └── ⚡ finally不一定执行 │ ├── join() │ ├── 等待目标线程执行完 │ ├── 底层是wait(),会释放锁 │ └── ⚡ vs sleep:join等别的线程,sleep等时间 │ ├── interrupt() │ ├── interrupt():设置中断标志 │ ├── isInterrupted():检查,不清标志 │ ├── interrupted():检查,清标志(静态方法) │ ├── 阻塞中被interrupt→抛InterruptedException+清标志 │ └── ⚡ 优雅停止线程的标准方式 │ └── 线程生命周期(6种状态) ├── NEW → start() → RUNNABLE ├── RUNNABLE ↔ BLOCKED(synchronized锁) ├── RUNNABLE ↔ WAITING(wait/join/park) ├── RUNNABLE ↔ TIMED_WAITING(sleep/wait(ms)/join(ms)) └── RUNNABLE → TERMINATED(run结束/异常)

写在最后

  1. start()和run()的区别几乎是100%会问的,必须脱口而出
  2. 三种创建方式要能对比出优劣,特别是为什么推荐Runnable
  3. 线程生命周期6种状态要能画出来,BLOCKED和WAITING的区别是常见追问
  4. interrupt机制是进阶内容,能说明白"协作式中断"这个概念很加分
  5. 守护线程的finally不保证执行——这是个反直觉的坑点,记住就是赚到
http://www.cnnetsun.cn/news/2953927.html

相关文章:

  • 2026年全铝大门选购避坑指南
  • 告别音乐平台切换烦恼:这款开源音乐聚合播放器如何改变你的听歌习惯?
  • 如何在Windows 11上完美运行安卓应用:WSABuilds完整安装指南
  • 基于NXP Harpoon框架的AVB音频管道实战配置与调试指南
  • 在Windows Hyper-V上免费安装macOS虚拟机的完整指南:5步搞定苹果系统
  • VALMET ND9106HX2/I02-A3 定位器工业实战应用指南
  • 创客匠人陪跑服务:打通知识 IP 变现最后一公里的落地模式
  • Java计算机毕设之基于 JavaWeb 的美食资源整合与推广交流系统设计 美食自媒体交流平台的设计与实现 (完整前后端代码+说明文档+LW,调试定制等)
  • 销量暴跌 57%!《每周工作 4 小时》作者血泪自剖:AI 正在杀死知识付费与工具书
  • 土木工程软件Civil 3D 2026超详细下载与安装教程指南
  • ZFX山海证券:把平台稳定性做扎实,长期观察者更容易感受到的方法
  • 构建高性能AMD GPU开发环境:ROCm实战配置与性能优化指南
  • ZigBee OTA升级:物联网设备固件无线更新的核心机制与工程实践
  • 如何管理WPS 2019的稻壳商城显示?一键关闭与快速开启指南
  • 终极Symbian模拟器EKA2L1:3步轻松在Windows、macOS、Linux和Android上重温N-Gage经典游戏
  • AI驱动的PDF转PPT技术解析:从“格式搬运”到“内容重构”的5款主流AI工具对比
  • ZigBee IAS ACE集群通信机制解析与智能安防系统开发实战
  • npm ERR! code CERT_HAS_EXPIRED:从证书链到系统时钟的全面排查指南
  • 调试器核心功能深度解析:从断点、事件点到程序执行控制
  • 如何为混沌测试编译跨平台Toxiproxy:Windows与ARM架构完整实战指南
  • PIC单片机超低功耗唤醒(ULPWU)原理与应用实战
  • QQScreenShot独立版:终极免费的QQ截图工具完整使用指南
  • Windows 11任务栏歌词显示终极指南:让音乐融入你的工作流 [特殊字符]
  • 国产大模型办公提效实战指南:通义千问、文心一言等备案模型应用解析
  • Video2X终极指南:三步免费将老旧视频无损升级到4K超高清
  • rfPIC12F675单端小环天线阻抗匹配实战:从理论计算到PCB调试
  • 深入解析PXD20 DCU3显示控制器:寄存器配置与嵌入式图形驱动开发
  • 为什么Blade模板引擎能缓存编译结果?
  • 赣州高口碑黄金铂金回收白银回收实体老店排行 5 家靠谱门店电话地址全收录
  • 深入解析CP-SAT混合约束求解引擎:3种架构设计与性能优化实战指南