Java学习26

一、上午 3 小时:多线程入门 + 第一种创建方式

1. 进程与线程核心概念(0.5h 精讲拓展)

1.1 进程

定义 :计算机中操作系统资源分配的最小独立单位 。每一个独立运行的程序都是一个进程(如 QQ、浏览器、IDEA)。特点

  • 拥有独立的内存空间、CPU 时间片等系统资源;
  • 进程之间完全隔离,互不共享数据;
  • 创建、销毁、切换开销极大
1.2 线程

定义 :进程内部的一条独立执行路径 ,也叫轻量级进程 。一个进程至少包含 1 个主线程(Java 程序 main 方法就是主线程)。特点

  • 共享所在进程的堆内存、方法区资源
  • 自身只独有栈内存;
  • 创建、切换开销极小,效率高。
1.3 核心区别总结(面试必背)

表格

维度 进程 线程
资源归属 独立资源,不共享 共享进程资源
开销 极小
通信难度 进程间通信复杂 线程间直接共享变量
隔离性 强,一个进程崩溃不影响其他 弱,一个线程异常可能导致整个进程崩溃
1.4 为什么要用多线程
  1. 提高 CPU 利用率:单线程 CPU 空闲等待,多线程可利用空闲时间;
  2. 并发处理任务:一边下载文件、一边播放视频,互不阻塞;
  3. 后台任务执行:主线程操作界面,子线程后台做耗时计算;
  4. 拆分耗时任务:把大任务拆分多线程并行执行,缩短耗时。

2. 并发与并行(0.3h 精讲拓展)

并行

定义同一时刻 ,多个任务同时执行条件 :依赖多核 CPU,每个核心单独跑一个线程,真正同时运行。例子:4 核 CPU 同时运行 4 个线程,同一瞬间都在工作。

并发

定义同一时间段 ,多个任务交替抢占 CPU 时间片 ,宏观看起来同时运行,微观是轮流执行。条件:单核 CPU 也能实现,CPU 快速切换线程。例子:食堂一个窗口,多人排队轮流打饭,时间段内都办成了事,但同一时刻只服务一人。

一句话区分 :并行是真同时 ,并发是假同时、快速交替


3. 方式一:继承 Thread 类创建线程(1.2h 精讲 + 完整代码案例)

核心步骤
  1. 自定义类 继承 Thread 父类
  2. 重写 run() 方法:书写线程要执行的任务代码
  3. 创建自定义线程类对象
  4. 调用 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();
    }
}
代码逐行解析
  1. MyThread extends Thread:自定义类继承线程父类,获得线程能力;
  2. 重写run():规定这个线程具体要做什么事
  3. new MyThread():只是创建对象,还没开启线程
  4. setName():给线程自定义名字,方便观察执行顺序;
  5. start():正式向 JVM 发起请求,创建新线程,CPU 随机调度两个线程交替执行;
  6. 运行结果无固定顺序,体现CPU 抢占式调度特点。

4. Thread 常用构造 & 基础方法(1h 精讲 + 代码实操)

常用构造方法
  1. Thread():无参构造,默认线程名 Thread-0、Thread-1...
  2. Thread(String name):带参构造,直接给线程命名
常用核心方法
  1. String getName():获取线程名称
  2. void setName(String name):设置线程名称
  3. static Thread currentThread()静态方法 ,获取当前正在执行的线程对象
  4. 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 精讲 + 完整代码)

实现步骤
  1. 自定义类 实现 Runnable 接口
  2. 重写 run() 方法,封装线程任务
  3. 创建 Runnable 任务对象
  4. 把任务对象传入 Thread 构造器
  5. 调用 start() 启动线程
面试必背优势(深度拓展)
  1. 规避 Java 单继承局限:类已经有父类时,无法再继承 Thread,但可以实现 Runnable;
  2. 适合多线程资源共享:同一个 Runnable 任务对象,可以传给多个 Thread,共享数据;
  3. 任务与线程解耦 :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 精讲 + 完整代码)

核心特点(前两种没有的能力)
  1. 线程执行完有返回值
  2. 重写的call()方法可以抛出异常
  3. 适合需要获取线程执行结果的场景(如异步计算、接口回调)。
实现步骤
  1. 自定义类 实现 Callable<V> 泛型接口,V 代表返回值类型
  2. 重写 V call() 方法,有返回值、可抛异常
  3. 创建 Callable 实现类对象
  4. FutureTask 包装 Callable 对象
  5. FutureTask 传入 Thread 构造,调用 start () 启动
  6. 调用 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中间包装器
  • 作用:
    1. 包装 Callable 任务
    2. 可以交给 Thread 线程使用
    3. 将来可以用 .get() 拿到线程的返回值
  • 泛型 <Integer> 和 Callable 保持一致,代表返回值类型

13. 创建线程对象

java

运行

复制代码
Thread t = new Thread(futureTask, "计算线程");
  • 创建系统自带的 Thread 线程对象
  • 第一个参数:传入包装好的 FutureTask 任务
  • 第二个参数:给线程起名叫「计算线程」
关键解析
  • Callable<Integer>:泛型指定返回值为整数;
  • call() 相比run() 多了返回值 + 异常抛出
  • futureTask.get()阻塞方法,主线程会等待子线程执行完,再拿到结果;
  • 适用场景:多线程异步计算、需要返回结果的耗时任务。

三、晚上 1.5h:线程优先级 + 生命周期 + 复盘

1. 线程优先级(0.5h 精讲)

  1. 优先级范围:1 ~ 10
  2. 常量定义:
    • Thread.MIN_PRIORITY = 1 最低优先级
    • Thread.NORM_PRIORITY = 5 默认优先级
    • Thread.MAX_PRIORITY = 10 最高优先级
  3. 常用方法:
    • 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 完整流转精讲)

五大固定状态:新建 → 就绪 → 运行 → 阻塞 → 死亡

  1. 新建状态 Newnew Thread 对象后,未调用 start (),仅创建对象,无线程资源。

  2. 就绪状态 Runnable 调用 start () 后,进入就绪队列,等待 CPU 调度,具备执行资格但没 CPU 时间片。

  3. 运行状态 RunningCPU 分配时间片,执行 run () 方法代码。

  4. 阻塞状态 Blocked线程暂停执行,放弃 CPU 时间片,等待唤醒:

    • sleep() 休眠
    • 等待同步锁
    • wait () 等待
  5. 死亡状态 Terminatedrun () 执行完毕 / 线程异常终止,线程销毁,释放资源,不能再重启。

状态流转核心场景
  • 运行 → 阻塞:调用 sleep ()、wait ()、等待锁;
  • 阻塞 → 就绪:sleep 时间到、被 notify () 唤醒、拿到同步锁;
  • 运行 → 死亡:代码执行结束、异常退出。

3. 今日必做练习(0.5h 任务清单)

  1. 手写 Thread、Runnable、Callable 三种线程创建完整代码;
  2. 给线程自定义名称、加入 sleep 休眠,观察交替执行;
  3. 设置高低不同优先级,反复运行观察抢占概率;
  4. 口述 5 大生命周期状态及互相切换条件。

四、原计划缺失重要知识点补充(必学,Day21 必须掌握)

原学习计划没包含,但多线程入门必考、后续必备知识点,全部补充:

  1. 主线程与子线程守护关系:守护线程(setDaemon ())概念、作用、应用场景;
  2. 匿名内部类创建线程:简化 Runnable 写法,实际开发常用;
  3. Lambda 表达式简化 Runnable:JDK8 后极简写法;
  4. 线程用户态与内核态简单认知
  5. 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 达标标准(强化版)

  1. 能完整口述进程、线程、并发、并行底层概念 + 区别;
  2. 能手写三种线程创建方式及匿名、Lambda 简化写法;
  3. 彻底分清 run()start() 底层本质区别;
  4. 熟练使用线程常用方法:sleep、命名、优先级、currentThread;
  5. 理解线程5 大生命周期状态流转及触发条件;
  6. 掌握守护线程概念及使用场景。

课外补充

java.lang 包下所有类,系统自动默认导入,不用手写 import,直接随便用

你天天用的这些,全在 java.lang,全都不用导包:

  • Thread、String、System、Object
  • Integer、Double、Boolean
  • RuntimeException、Exception
  • Math

Day26 今日学习内容 超清晰汇总(全考点 + 易混点 + 规则 + 代码要点)

一、核心基础概念

  1. 进程 & 线程
  • 进程:操作系统资源分配最小单位,独立内存、开销大、互不共享。
  • 线程:进程内执行路径,轻量级进程,共享进程资源、开销小、切换快。
  • 一个进程至少有 1 个主线程(Java main 主线程)。
  1. 并发 & 并行
  • 并行:同一时刻多核 CPU 真正同时执行多个线程。
  • 并发:同一时间段CPU 快速交替执行,宏观像同时跑,微观轮流抢时间片。
  1. 多线程作用提高 CPU 利用率、后台并发任务、拆分耗时任务、不阻塞主线程。

二、线程三种创建方式(必背 + 代码要点)

方式一:继承 Thread 类

步骤:自定义类 → 继承 Thread → 重写 run () → 创建对象 → 调用 start ()重点:

  • run ():普通方法,不开启新线程
  • start ():申请开辟新线程,自动执行 run ()
  • 一个线程对象只能调用一次 start ()

方式二:实现 Runnable 接口

步骤:自定义类 → 实现 Runnable → 重写 run () → 任务对象传入 Thread → start ()面试优势:

  • 规避 Java 单继承局限
  • 适合多线程资源共享
  • 任务和线程解耦

方式三:Callable + FutureTask

特点:

  • 有返回值、可以抛出异常步骤:实现 Callable 泛型接口 → 重写 call () → FutureTask 包装 → 传入 Thread 启动 → get () 拿返回值
  • get () 是阻塞方法,主线程等子线程执行完才往下走

三、Thread 常用方法

  1. getName () /setName ():获取 / 设置线程名
  2. currentThread ():静态方法,获取当前正在执行的线程
  3. sleep (毫秒):静态休眠,暂停当前线程,让出 CPU 时间片
    • 属于静态方法 :规范写法 Thread.sleep()
    • 会抛出受检异常

四、线程优先级

  • 范围:1~10,默认 5
  • 三个常量:MIN (1)、NORM (5)、MAX (10)
  • 规则:优先级越高抢到 CPU 时间片概率越大,不是绝对先执行

五、线程 5 大生命周期

新建 → 就绪 → 运行 → 阻塞 → 死亡

  • 新建:new 线程对象,未 start
  • 就绪:调用 start (),等 CPU 调度
  • 运行:抢到时间片执行 run
  • 阻塞:sleep、等待锁、wait 等,让出 CPU
  • 死亡:代码跑完 / 异常终止,线程销毁

六、今日最易混淆 核心规则(必记)

  1. Java 类规则 一个 java 文件只能有一个 public class,public 类名必须和文件名一致,其他类不加 public。

  2. 父类子类 & 重写异常规则

  • 你写的 MyThread 是子类 ,系统自带 Thread 是父类
  • 父类 Thread 的 run ()没有 throws 异常
  • 子类重写 run ()不能声明 throws,只能用 try-catch 处理异常
  • Callable 的 call () 接口自带 throws,所以可以直接抛,不用 try
  1. super.run() IDEA 自动生成的,父类 run () 是空方法,直接删掉,写自己业务代码即可。

  2. **为什么简单循环每次运行顺序都一样?**循环执行太快,CPU 一次性跑完一个线程,加 sleep 休眠后才会看到交替抢占效果。

  3. 静态方法 sleep sleep 是 static 静态方法,属于 Thread 类,规范必须写 Thread.sleep() 调用。

七、Day21 必达标清单

  1. 能口述进程、线程、并发、并行概念与区别
  2. 能手写三种线程创建方式
  3. 分清 run () 和 start () 本质区别
  4. 会用线程起名、sleep、优先级、currentThread
  5. 懂线程 5 大状态流转
  6. 牢记重写异常规则、一个文件一个 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() 阻塞获取返回值
常用场景 简单快速写临时线程 日常开发最常用、适合后台任务、资源共享 需要拿到线程执行结果、异步计算场景

一句话快速区分

  1. 继承 Thread:写法最简单,受单继承限制,无返回值;
  2. Runnable:开发首选,无继承限制、支持资源共享,无返回值;
  3. Callable:有返回值、能抛异常,适合需要拿执行结果的场景。

额外必背考点

  1. 前两种 run() 都没返回值、不能抛受检异常;只有 call() 可以有返回值 + 直接抛异常。
  2. Runnable 接口最推荐,实际工作中几乎都用它或 Lambda 简化写法。
  3. Callable 不能直接传给 Thread,必须用 FutureTask 包装 才能启动。
相关推荐
qeen871 小时前
【数据结构】二叉树相关经典函数C语言实现
c语言·数据结构·c++·笔记·学习·算法·二叉树
dingxingdi1 小时前
如何学习一个新的 Coding CLI 工具
学习
伏加特遇上西柚2 小时前
Loki+Alloy+Grafana日志采集部署
java·linux·服务器·spring boot·grafana·prometheus
Alice-YUE2 小时前
深入解析 JS 事件循环:浏览器与 Node.js 的差异全解析
前端·javascript·笔记·学习
minglie12 小时前
UG585Address Map
学习
阿丘Akiu2 小时前
Linux部署我的世界服务器
java
远离UE42 小时前
Vulkan学习笔记
笔记·学习
折哥的程序人生 · 物流技术专研2 小时前
《Java面试85题图解版(二)》进阶深化中篇:Spring核心 + 数据库进阶
java·后端·spring·面试
SamDeepThinking2 小时前
写代码不考虑前后兼容,迟早要还的
java·后端·程序员