本阶段涵盖:多线程入门、线程操作与线程安全、volatile/Lock 锁/线程池 细分文档:
按日细分/20260808-笔记.md(多线程入门部分)、按日细分/20260810-笔记.md、按日细分/20260811-笔记.md学生需要重点理解、背诵、掌握的理论核心知识点。
| 概念 | 说明 |
|---|---|
| 程序 | 硬盘中的文件,静态代码 |
| 进程 | 内存中正在执行的程序,占用 CPU/内存等资源 |
| 线程 | 进程中的单个顺序控制流,一条执行路径;一个进程可含多线程 |
| 概念 | 含义 | 类比 |
|---|---|---|
| 并行 | 同一时刻多指令在多个 CPU 上同时执行 | 3 灶台 3 厨师 |
| 并发 | 同一时刻多指令在单个 CPU 上交替执行 | 3 灶台 1 厨师 |
方式一:继承 Thread 步骤:①继承 Thread ②重写 run() ③创建对象 ④start() 启动
方式二:实现 Runnable(更推荐)
步骤:①implements Runnable ②重写 run() ③创建对象 ④new Thread(对象, "线程名") ⑤start()
Thread t = new Thread(new RunnableImpl(), "光头强线程!");
t.start();
为什么更推荐: 不占单继承位、可多实现接口、任务与线程解耦(组合优于继承)。 三种提供方式:实现类对象 / 匿名内部类 / Lambda(Runnable 是函数式接口)。
方式三:实现 Callable + FutureTask(可返回结果)
步骤:①implements Callable<V> ②重写 call() ③创建对象 ④new FutureTask<>(callable) ⑤new Thread(futureTask) ⑥start() ⑦get() 获取结果
call() 带返回值(对比 run() 无返回值)FutureTask 是"中间人";get() 阻塞等待计算结果三种方式对比:
| 维度 | 继承 Thread | 实现 Runnable | 实现 Callable |
|------|------------|--------------|---------------|
| 返回结果 | 无 | 无 | 有 |
| 扩展性 | 差 | 强 | 强 |
| 直接用 Thread 方法 | 可以(this 是线程) | Thread.currentThread() | 同左 |
| 推荐 | 入门 | 更推荐 | 需返回结果 |
Thread 常用方法: setName/getName、Thread.currentThread()(静态,返回当前线程对象)
Thread.sleep(毫秒):静态方法,当前线程休眠(抛 InterruptedException)getPriority()(默认 5)/ setPriority(int)(范围 1~10)
setDaemon(true) 必须在 start() 之前,否则 IllegalThreadStateException| 状态 | 枚举 | 触发 |
|---|---|---|
| 新建 | NEW | new Thread() 未 start() |
| 可运行 | RUNNABLE | start() 后运行或等时间片 |
| 阻塞 | BLOCKED | 抢同步锁失败 |
| 等待 | WAITING | wait() / join() / park() |
| 计时等待 | TIMED_WAITING | sleep(ms) / wait(timeout) / join(timeout) |
| 终止 | TERMINATED | run() 结束(终点站) |
| 方法 | 作用 |
|---|---|
join() / join(ms) |
当前线程等待目标线程结束 / 限时等待 |
interrupt() |
设置中断标志(不杀线程);sleep/wait/join 中抛异常并清标志 |
yield() |
主动让出 CPU(只是建议) |
isAlive() |
判断线程是否存活 |
wait() / notify() / notifyAll() |
Object 类方法:等待/唤醒(需持有锁) |
interrupt 机制:只设标志、请求线程自行响应;isInterrupted() 主动检查;catch 后重设标志(Thread.currentThread().interrupt())。
线程间共享数据和内存。多线程同时读写同一共享变量 → 结果不可预测、错误无法复现。
count++ 不是原子操作(读-改-写 3 步),理论 1000 实际小于 1000| 特性 | 含义 | 解决方案 |
|---|---|---|
| 原子性 | 操作不可分割 | synchronized、Lock、Atomic 类 |
| 可见性 | 修改对另一线程立即可见 | volatile、synchronized、Lock |
| 有序性 | 代码按书写顺序执行 | volatile、synchronized |
口诀"原可序":原子性、可见性、有序性。
三种写法:
| 写法 | 锁的是什么 |
|------|-----------|
| 同步实例方法 synchronized void m() | 当前对象 this |
| 同步静态方法 static synchronized void m() | 类的 Class 对象 |
| 同步代码块 synchronized(lock) | 指定的任意对象(最灵活) |
转账场景:"检查-修改"必须整体锁住(if (balance >= amount) + balance -= amount 用 synchronized(this) 包住)。
volatile count++ 依旧丢更新——必须用 synchronized/Lock/原子类| 对比 | volatile | synchronized |
|---|---|---|
| 本质 | 轻量、不加锁 | 锁机制(Monitor Lock) |
| 解决 | 可见性 + 有序性 | 三大特性全部 |
| 适用 | 标志位 | 复合操作 |
验证:volatile 修饰 count 后 count++ 结果照样 < 1000(只解决可见性);加 synchronized 后必然 = 1000。
JDK 1.5 提供 Lock 接口 + ReentrantLock 实现,比 synchronized 更精细:
new ReentrantLock(true) 公平锁)synchronized 自动释放锁;Lock 必须手动 unlock() 且放 finally——否则抛异常/return 时锁永不释放 → 死锁。
lock.lock();
try { count++; }
finally { lock.unlock(); }
lock() / unlock() / tryLock()(拿不到立即返回 false)/ tryLock(timeout, unit)(限时)/ lockInterruptibly()(可中断)
| 维度 | synchronized | ReentrantLock |
|---|---|---|
| 语法 | 简单(关键字) | 复杂(try-finally) |
| 释放 | 自动 | 手动 unlock |
| 可中断 | 不支持 | 支持 |
| 超时等待 | 不支持 | 支持 |
| 锁读 | 不支持 | 支持(读写分离) |
| 底层 | 监视器锁 Monitor Lock | AQS 抽象队列同步器 |
| JDK21 | 大幅优化 | 性能相近 |
概念:两个或多个线程相互持有对方所需的资源,导致等待无法继续。
| 方法 | 打破的条件 |
|---|---|
| 固定锁顺序(所有线程按相同顺序加锁) | 打破"循环等待" |
| tryLock() 超时获取(拿不到就放弃) | 打破"持有且等待" |
| 高级并发工具(如 ConcurrentHashMap) | 从源头避免手动加多把锁 |
排查死锁:
jstack命令找Found one Java-level deadlock。
解决方案:【线程复用 + 任务排队 + 动态扩容 + 上限保护】
| 参数 | 说明 |
|---|---|
| corePoolSize | 核心线程数(常驻) |
| maximumPoolSize | 最大线程数(必须 > 核心) |
| keepAliveTime | 非核心线程空闲存活时间 |
| unit | 时间单位 |
| workQueue | 工作队列 |
| threadFactory | 线程工厂 |
| handler | 拒绝策略 |
核心线程 → 工作队列 → 非核心线程 → 拒绝策略
线程不够不先创建新线程,先放工作队列;队列满且未达最大线程数才扩容。
AbortPolicy(默认抛异常)/ CallerRunsPolicy(调用者执行)/ DiscardPolicy(丢弃)/ DiscardOldestPolicy(丢最旧)
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, 5, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
pool.execute(runnable); // 复用线程,不是 new Thread
pool.shutdown(); // 关闭线程池
⚠️ 线程池解决"创建开销"问题,不解决"数据竞争"问题(池中线程处理共享数据仍需锁)。