一、上午 3 小时:多线程入门 + 第一种创建方式
1. 进程与线程核心概念(0.5h 精讲拓展)
1.1 进程
定义 :计算机中操作系统资源分配的最小独立单位 。每一个独立运行的程序都是一个进程(如 QQ、浏览器、IDEA)。特点:
- 拥有独立的内存空间、CPU 时间片等系统资源;
- 进程之间完全隔离,互不共享数据;
- 创建、销毁、切换开销极大。
1.2 线程
定义 :进程内部的一条独立执行路径 ,也叫轻量级进程 。一个进程至少包含 1 个主线程(Java 程序 main 方法就是主线程)。特点:
- 共享所在进程的堆内存、方法区资源;
- 自身只独有栈内存;
- 创建、切换开销极小,效率高。
1.3 核心区别总结(面试必背)
表格
| 维度 | 进程 | 线程 |
|---|---|---|
| 资源归属 | 独立资源,不共享 | 共享进程资源 |
| 开销 | 大 | 极小 |
| 通信难度 | 进程间通信复杂 | 线程间直接共享变量 |
| 隔离性 | 强,一个进程崩溃不影响其他 | 弱,一个线程异常可能导致整个进程崩溃 |
1.4 为什么要用多线程
- 提高 CPU 利用率:单线程 CPU 空闲等待,多线程可利用空闲时间;
- 并发处理任务:一边下载文件、一边播放视频,互不阻塞;
- 后台任务执行:主线程操作界面,子线程后台做耗时计算;
- 拆分耗时任务:把大任务拆分多线程并行执行,缩短耗时。
2. 并发与并行(0.3h 精讲拓展)
并行
定义 :同一时刻 ,多个任务同时执行 。条件 :依赖多核 CPU,每个核心单独跑一个线程,真正同时运行。例子:4 核 CPU 同时运行 4 个线程,同一瞬间都在工作。
并发
定义 :同一时间段 ,多个任务交替抢占 CPU 时间片 ,宏观看起来同时运行,微观是轮流执行。条件:单核 CPU 也能实现,CPU 快速切换线程。例子:食堂一个窗口,多人排队轮流打饭,时间段内都办成了事,但同一时刻只服务一人。
一句话区分 :并行是真同时 ,并发是假同时、快速交替。
3. 方式一:继承 Thread 类创建线程(1.2h 精讲 + 完整代码案例)
核心步骤
- 自定义类 继承 Thread 父类
- 重写
run()方法:书写线程要执行的任务代码 - 创建自定义线程类对象
- 调用
start()方法启动线程 ,不能直接调用run()
重点考点深度解析
run():只是一个普通成员方法 ,直接调用就是普通顺序执行,不会开启新线程;start():向 JVM 申请开辟新栈内存 、开启新线程,由 CPU 调度自动执行run()方法;- 一个线程对象只能调用一次 start () ,重复调用会抛出
IllegalThreadStateException异常。
完整代码案例
java
运行
// 1. 自定义线程类,继承Thread
public class MyThread extends Thread {
// 2. 重写run方法,定义线程任务
@Override
public void run() {
// 线程要执行的循环打印任务
for (int i = 1; i <= 5; i++) {
// getName():获取当前线程名称
System.out.println(getName() + " 执行:" + i);
}
}
}
// 测试类
public class ThreadTest {
public static void main(String[] args) {
// 3. 创建线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// 设置线程名称
t1.setName("线程一号");
t2.setName("线程二号");
// 4. 调用start()启动线程,开启新路径
t1.start();
t2.start();
}
}
代码逐行解析
MyThread extends Thread:自定义类继承线程父类,获得线程能力;- 重写
run():规定这个线程具体要做什么事; new MyThread():只是创建对象,还没开启线程;setName():给线程自定义名字,方便观察执行顺序;start():正式向 JVM 发起请求,创建新线程,CPU 随机调度两个线程交替执行;- 运行结果无固定顺序,体现CPU 抢占式调度特点。
4. Thread 常用构造 & 基础方法(1h 精讲 + 代码实操)
常用构造方法
Thread():无参构造,默认线程名 Thread-0、Thread-1...Thread(String name):带参构造,直接给线程命名
常用核心方法
String getName():获取线程名称void setName(String name):设置线程名称static Thread currentThread():静态方法 ,获取当前正在执行的线程对象static void sleep(long millis):静态休眠,让当前线程暂停指定毫秒,让出 CPU 时间片
拓展代码案例(含 sleep 休眠)
java
运行
public class ThreadMethodDemo extends Thread {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(getName() + " 第" + i + "次执行");
try {
// 线程休眠1000毫秒 = 1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
// 线程被中断时抛出异常
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ThreadMethodDemo t = new ThreadMethodDemo();
t.setName("休眠线程");
t.start();
// currentThread() 获取主线程对象
System.out.println("主线程名称:" + Thread.currentThread().getName());
}
}
因为 sleep 是静态方法 → 必须用 类名.方法名 () 调用!即Thread.sleep()这样写
解析
Thread.sleep(1000):当前线程强制休眠 1 秒,期间放弃 CPU 执行权;- 休眠会抛出受检异常,必须 try-catch 捕获;
currentThread()在 main 方法中使用,获取的就是主线程 main。
二、下午 2.5 小时:另外两种线程创建方式
1. 方式二:实现 Runnable 接口(1.2h 精讲 + 完整代码)
实现步骤
- 自定义类 实现 Runnable 接口
- 重写
run()方法,封装线程任务 - 创建 Runnable 任务对象
- 把任务对象传入
Thread构造器 - 调用
start()启动线程
面试必背优势(深度拓展)
- 规避 Java 单继承局限:类已经有父类时,无法再继承 Thread,但可以实现 Runnable;
- 适合多线程资源共享:同一个 Runnable 任务对象,可以传给多个 Thread,共享数据;
- 任务与线程解耦 :Runnable 只负责定义任务 ,Thread 只负责作为线程载体,分工明确。
完整代码案例
java
运行
// 1. 实现Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程任务
for (int i = 1; i <= 5; i++) {
// 获取当前执行线程的名称
System.out.println(Thread.currentThread().getName() + " 打印:" + i);
}
}
}
// 测试类
public class RunnableTest {
public static void main(String[] args) {
// 2. 创建任务对象
MyRunnable runnable = new MyRunnable();
// 3. 任务对象传入Thread构造
Thread t1 = new Thread(runnable, "线程A");
Thread t2 = new Thread(runnable, "线程B");
// 4. 启动线程
t1.start();
t2.start();
}
}
代码解析
MyRunnable只负责写任务逻辑,和线程类解绑;- 同一个
runnable对象传给两个线程,实现任务复用; - 通过
Thread.currentThread().getName()获取线程名,因为 Runnable 没有 getName 方法。
2. 方式三:Callable + FutureTask(1.3h 精讲 + 完整代码)
核心特点(前两种没有的能力)
- 线程执行完有返回值;
- 重写的
call()方法可以抛出异常; - 适合需要获取线程执行结果的场景(如异步计算、接口回调)。
实现步骤
- 自定义类 实现 Callable<V> 泛型接口,V 代表返回值类型
- 重写
V call()方法,有返回值、可抛异常 - 创建 Callable 实现类对象
- 用
FutureTask包装 Callable 对象 - FutureTask 传入 Thread 构造,调用 start () 启动
- 调用
futureTask.get()获取线程返回结果
完整代码案例
java
运行
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
// 1. 实现Callable接口,泛型Integer表示返回值类型
public class MyCallable implements Callable<Integer> {
@Override
// 2. 重写call方法,有返回值、可抛异常
public Integer call() throws Exception {
int sum = 0;
// 计算1~10的和
for (int i = 1; i <= 10; i++) {
sum += i;
System.out.println(Thread.currentThread().getName() + " 计算:" + i);
}
// 返回计算结果
return sum;
}
}
// 测试类
public class CallableTest {
public static void main(String[] args) throws Exception {
// 3. 创建Callable对象
MyCallable myCallable = new MyCallable();
// 4. FutureTask包装Callable
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
// 5. 传入Thread启动
Thread t = new Thread(futureTask, "计算线程");
t.start();
// 6. get()获取线程返回结果,会阻塞主线程直到子线程执行完毕
Integer result = futureTask.get();
System.out.println("1~10累加和:" + result);
}
}
public Integer call() throws Exception
call():Callable 接口里唯一的抽象方法,相当于 Thread 的 run ()
难点解释
. FutureTask 包装
java
运行
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
FutureTask是中间包装器- 作用:
- 包装 Callable 任务
- 可以交给 Thread 线程使用
- 将来可以用
.get()拿到线程的返回值
- 泛型
<Integer>和 Callable 保持一致,代表返回值类型
13. 创建线程对象
java
运行
Thread t = new Thread(futureTask, "计算线程");
- 创建系统自带的 Thread 线程对象
- 第一个参数:传入包装好的 FutureTask 任务
- 第二个参数:给线程起名叫「计算线程」
关键解析
Callable<Integer>:泛型指定返回值为整数;call()相比run()多了返回值 + 异常抛出;futureTask.get():阻塞方法,主线程会等待子线程执行完,再拿到结果;- 适用场景:多线程异步计算、需要返回结果的耗时任务。
三、晚上 1.5h:线程优先级 + 生命周期 + 复盘
1. 线程优先级(0.5h 精讲)
- 优先级范围:1 ~ 10
- 常量定义:
Thread.MIN_PRIORITY= 1 最低优先级Thread.NORM_PRIORITY= 5 默认优先级Thread.MAX_PRIORITY= 10 最高优先级
- 常用方法:
void setPriority(int newPriority)设置优先级int getPriority()获取当前优先级
核心原理
优先级越高,获取 CPU 时间片的概率越大 ,不是绝对优先执行;CPU 是抢占式调度,高优先级只是抢到资源几率更高,不能控制执行顺序。
代码案例
java
运行
public class PriorityDemo extends Thread {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(getName() + " :" + i);
}
}
public static void main(String[] args) {
PriorityDemo t1 = new PriorityDemo();
PriorityDemo t2 = new PriorityDemo();
t1.setName("高优先级线程");
t2.setName("低优先级线程");
// 设置优先级
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
2. 线程生命周期 5 大状态(0.5h 完整流转精讲)
五大固定状态:新建 → 就绪 → 运行 → 阻塞 → 死亡
-
新建状态 Newnew Thread 对象后,未调用 start (),仅创建对象,无线程资源。
-
就绪状态 Runnable 调用 start () 后,进入就绪队列,等待 CPU 调度,具备执行资格但没 CPU 时间片。
-
运行状态 RunningCPU 分配时间片,执行 run () 方法代码。
-
阻塞状态 Blocked线程暂停执行,放弃 CPU 时间片,等待唤醒:
sleep()休眠- 等待同步锁
- wait () 等待
-
死亡状态 Terminatedrun () 执行完毕 / 线程异常终止,线程销毁,释放资源,不能再重启。
状态流转核心场景
- 运行 → 阻塞:调用 sleep ()、wait ()、等待锁;
- 阻塞 → 就绪:sleep 时间到、被 notify () 唤醒、拿到同步锁;
- 运行 → 死亡:代码执行结束、异常退出。
3. 今日必做练习(0.5h 任务清单)
- 手写 Thread、Runnable、Callable 三种线程创建完整代码;
- 给线程自定义名称、加入 sleep 休眠,观察交替执行;
- 设置高低不同优先级,反复运行观察抢占概率;
- 口述 5 大生命周期状态及互相切换条件。
四、原计划缺失重要知识点补充(必学,Day21 必须掌握)
原学习计划没包含,但多线程入门必考、后续必备知识点,全部补充:
- 主线程与子线程守护关系:守护线程(setDaemon ())概念、作用、应用场景;
- 匿名内部类创建线程:简化 Runnable 写法,实际开发常用;
- Lambda 表达式简化 Runnable:JDK8 后极简写法;
- 线程用户态与内核态简单认知;
- start () 多次调用异常原理。
补充 1:守护线程(后台线程)
- 普通线程:用户线程,主线程结束会等待子线程执行完再退出;
- 守护线程:后台线程,所有用户线程结束,守护线程自动终止;
- 方法:
void setDaemon(true)设置为守护线程,必须在 start () 之前调用。
补充 2:匿名内部类创建线程(开发常用)
java
运行
// 继承Thread匿名写法
new Thread(){
@Override
public void run() {
System.out.println("匿名线程执行");
}
}.start();
// 实现Runnable匿名写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Runnable匿名任务");
}
}).start();
补充 3:Lambda 简化 Runnable(JDK8+)
java
运行
new Thread(() -> System.out.println("Lambda简化多线程")).start();
五、Day26 达标标准(强化版)
- 能完整口述进程、线程、并发、并行底层概念 + 区别;
- 能手写三种线程创建方式及匿名、Lambda 简化写法;
- 彻底分清
run()和start()底层本质区别; - 熟练使用线程常用方法:sleep、命名、优先级、currentThread;
- 理解线程5 大生命周期状态流转及触发条件;
- 掌握守护线程概念及使用场景。
课外补充
java.lang 包下所有类,系统自动默认导入,不用手写 import,直接随便用
你天天用的这些,全在 java.lang,全都不用导包:
- Thread、String、System、Object
- Integer、Double、Boolean
- RuntimeException、Exception
- Math
Day26 今日学习内容 超清晰汇总(全考点 + 易混点 + 规则 + 代码要点)
一、核心基础概念
- 进程 & 线程
- 进程:操作系统资源分配最小单位,独立内存、开销大、互不共享。
- 线程:进程内执行路径,轻量级进程,共享进程资源、开销小、切换快。
- 一个进程至少有 1 个主线程(Java main 主线程)。
- 并发 & 并行
- 并行:同一时刻多核 CPU 真正同时执行多个线程。
- 并发:同一时间段CPU 快速交替执行,宏观像同时跑,微观轮流抢时间片。
- 多线程作用提高 CPU 利用率、后台并发任务、拆分耗时任务、不阻塞主线程。
二、线程三种创建方式(必背 + 代码要点)
方式一:继承 Thread 类
步骤:自定义类 → 继承 Thread → 重写 run () → 创建对象 → 调用 start ()重点:
- run ():普通方法,不开启新线程
- start ():申请开辟新线程,自动执行 run ()
- 一个线程对象只能调用一次 start ()
方式二:实现 Runnable 接口
步骤:自定义类 → 实现 Runnable → 重写 run () → 任务对象传入 Thread → start ()面试优势:
- 规避 Java 单继承局限
- 适合多线程资源共享
- 任务和线程解耦
方式三:Callable + FutureTask
特点:
- 有返回值、可以抛出异常步骤:实现 Callable 泛型接口 → 重写 call () → FutureTask 包装 → 传入 Thread 启动 → get () 拿返回值
- get () 是阻塞方法,主线程等子线程执行完才往下走
三、Thread 常用方法
- getName () /setName ():获取 / 设置线程名
- currentThread ():静态方法,获取当前正在执行的线程
- sleep (毫秒):静态休眠,暂停当前线程,让出 CPU 时间片
- 属于静态方法 :规范写法
Thread.sleep() - 会抛出受检异常
- 属于静态方法 :规范写法
四、线程优先级
- 范围:1~10,默认 5
- 三个常量:MIN (1)、NORM (5)、MAX (10)
- 规则:优先级越高抢到 CPU 时间片概率越大,不是绝对先执行
五、线程 5 大生命周期
新建 → 就绪 → 运行 → 阻塞 → 死亡
- 新建:new 线程对象,未 start
- 就绪:调用 start (),等 CPU 调度
- 运行:抢到时间片执行 run
- 阻塞:sleep、等待锁、wait 等,让出 CPU
- 死亡:代码跑完 / 异常终止,线程销毁
六、今日最易混淆 核心规则(必记)
-
Java 类规则 一个 java 文件只能有一个 public class,public 类名必须和文件名一致,其他类不加 public。
-
父类子类 & 重写异常规则
- 你写的 MyThread 是子类 ,系统自带 Thread 是父类
- 父类 Thread 的 run ()没有 throws 异常
- 子类重写 run ()不能声明 throws,只能用 try-catch 处理异常
- Callable 的 call () 接口自带 throws,所以可以直接抛,不用 try
-
super.run() IDEA 自动生成的,父类 run () 是空方法,直接删掉,写自己业务代码即可。
-
**为什么简单循环每次运行顺序都一样?**循环执行太快,CPU 一次性跑完一个线程,加 sleep 休眠后才会看到交替抢占效果。
-
静态方法 sleep sleep 是 static 静态方法,属于 Thread 类,规范必须写
Thread.sleep()调用。
七、Day21 必达标清单
- 能口述进程、线程、并发、并行概念与区别
- 能手写三种线程创建方式
- 分清 run () 和 start () 本质区别
- 会用线程起名、sleep、优先级、currentThread
- 懂线程 5 大状态流转
- 牢记重写异常规则、一个文件一个 public 类规则
Java 三种线程创建方式 超清晰对比表
表格
| 对比维度 | 方式一:继承 Thread 类 | 方式二:实现 Runnable 接口 | 方式三:Callable + FutureTask |
|---|---|---|---|
| 实现形式 | 自定义类 extends Thread | 自定义类 implements Runnable | 自定义类 implements Callable<V> |
| 重写方法 | 重写 run() | 重写 run() | 重写 call() |
| 返回值 | 无返回值 | 无返回值 | 有返回值(泛型指定类型) |
| 异常处理 | run () 不能抛异常,只能 try-catch |
run () 不能抛异常,只能 try-catch |
call() 可以直接 throws 异常,不用 try |
| 单继承局限 | 有局限性:Java 只能单继承,继承了 Thread 就不能再继承其他父类 | 无局限:接口多实现,不占用继承名额 | 无局限:接口多实现 |
| 资源共享 | 不适合多线程共享同一个任务 | 适合资源共享,同一个任务对象可传给多个 Thread | 适合有返回值的任务共享 |
| 任务与线程耦合 | 耦合度高:任务和线程绑在一起 | 解耦:任务单独拆分,和线程分离 | 解耦,专门用于异步带返回值任务 |
| 启动方式 | 直接创建子类对象,调用 start() |
任务对象传入 Thread 构造器,再 start() |
用 FutureTask 包装后传入 Thread,再 start() |
| 获取结果 | 无法获取线程执行结果 | 无法获取线程执行结果 | 调用 futureTask.get() 阻塞获取返回值 |
| 常用场景 | 简单快速写临时线程 | 日常开发最常用、适合后台任务、资源共享 | 需要拿到线程执行结果、异步计算场景 |
一句话快速区分
- 继承 Thread:写法最简单,受单继承限制,无返回值;
- Runnable:开发首选,无继承限制、支持资源共享,无返回值;
- Callable:有返回值、能抛异常,适合需要拿执行结果的场景。
额外必背考点
- 前两种
run()都没返回值、不能抛受检异常;只有call()可以有返回值 + 直接抛异常。 - Runnable 接口最推荐,实际工作中几乎都用它或 Lambda 简化写法。
- Callable 不能直接传给 Thread,必须用 FutureTask 包装 才能启动。