作者按:本文保证让你笑着学会多线程!如果看完还不懂,那一定是我写得不够好笑!😎
📚 目录
- 开篇:多线程是个啥玩意儿?
- 生活中的多线程:你早就在用了!
- 线程家族谱:Thread和Runnable的爱恨情仇
- 线程的一生:从出生到退休
- 线程同步:别抢了,排队!
- 线程池:线程界的劳务派遣公司
- 常见坑点:别踩雷!
- 实战演练:手把手教学
- 高级技巧:从青铜到王者
🎯 开篇:多线程是个啥玩意儿?
🤔 官方定义 vs 人话翻译
官方定义:多线程是指在一个程序中同时运行多个线程,每个线程可以独立执行不同的任务。
人话翻译:就像你一边吃火锅🍲一边刷抖音📱一边聊微信💬,同时干三件事,效率杠杠的!
🎭 超形象比喻
想象你开了一家餐厅🍽️:
单线程餐厅(惨淡经营版):
老板一人干所有活 → 点菜、做菜、上菜、收钱
顾客A点菜 → 老板去做 → 做完上菜 → 收钱
顾客B点菜 → 等顾客A吃完才能点 😭
结果:顾客排长队,生意惨淡
多线程餐厅(火爆经营版):
👨🍳 厨师线程:专心做菜
🧑💼 服务员线程:点菜、上菜
💰 收银员线程:收钱找零
📦 洗碗工线程:洗碗打扫
顾客A、B、C、D同时服务 🎉
结果:效率爆表,赚翻了!💰💰💰
💡 为什么要用多线程?
| 场景 | 单线程 😭 | 多线程 🎉 |
|---|---|---|
| 下载文件 | 下载时界面卡死,啥也干不了 | 后台下载,该干嘛干嘛 |
| 玩游戏 | 画面、音效、操作排队执行,卡成PPT | 流畅运行,60帧丝滑 |
| 处理订单 | 一次处理一个,其他人等着 | 同时处理上千个订单 |
| 网页加载 | 一张图片一张图片加载,慢死了 | 图片同时加载,秒开 |
结论:多线程 = 效率 + 体验 + 赚钱!💪
🏠 生活中的多线程:你早就在用了!
例子1:早晨起床的多线程操作 ⏰
7:00 AM 闹钟响了!
单线程模式(笨蛋模式):
7:00-7:10 刷牙洗脸 🦷
7:10-7:20 煮早餐 🍳
7:20-7:30 吃早餐 🥪
7:30-7:40 换衣服 👔
7:40-8:00 出门通勤 🚗
总耗时:60分钟 → 迟到!😱
多线程模式(聪明模式):
7:00 【线程1】煮豆浆(自动进行,不用管)🥛
7:00 【线程2】刷牙洗脸 🦷
7:10 【线程3】吃早餐(豆浆已经煮好)🥪
7:20 【线程4】换衣服的同时听新闻 👔📻
7:30 【线程5】出门通勤 🚗
总耗时:30分钟 → 从容淡定!😎
例子2:餐厅点菜的多线程 🍽️
场景:你和朋友去火锅店
单线程服务(糟糕体验):
服务员:先给1号桌点菜 → 去后厨下单 → 等菜做好
→ 上菜 → 再给2号桌点菜 → ...
你:等了1小时才轮到我们点菜 💢
结果:差评!再也不来了!⭐
多线程服务(完美体验):
【服务员A线程】负责1-5号桌点菜
【服务员B线程】负责6-10号桌点菜
【厨师C线程】炒菜
【厨师D线程】煮火锅
【服务员E线程】上菜
【收银员F线程】结账
你:5分钟就点上菜了!赞!👍
结果:五星好评!🌟🌟🌟🌟🌟
例子3:超市收银的多线程 🛒
超市场景:20个顾客要结账
单线程(一个收银台):
顾客1: 扫码...等待...付款 (3分钟)
顾客2: 排队等待... 😤
顾客3: 排队等待... 😡
...
顾客20: 等了1小时,疯了!🤬
总耗时:60分钟
多线程(5个收银台):
收银台1: 顾客1、5、9、13、17 同时处理
收银台2: 顾客2、6、10、14、18 同时处理
收银台3: 顾客3、7、11、15、19 同时处理
收银台4: 顾客4、8、12、16、20 同时处理
收银台5: 备用(高峰时启动)
总耗时:12分钟 ⚡
效率提升:5倍!这就是多线程的魔力!✨
核心思想:能同时做的事,就不要排队做!
👨👩👦 线程家族谱:Thread和Runnable的爱恨情仇
🎭 家族成员介绍
线程大家族
|
┌───────┴───────┐
| |
Thread类 Runnable接口
(亲儿子) (养子)
| |
继承方式 实现方式
(独占继承) (灵活多继承)
方式1️⃣:继承Thread类(霸道总裁)
特点:简单粗暴,但只能单身(Java不支持多重继承)
// 定义一个线程类(就像生了个孩子)
class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
// 线程要干的事情写在这里
for (int i = 1; i <= 5; i++) {
System.out.println(name + "正在执行第" + i + "次任务 🏃");
try {
Thread.sleep(1000); // 休息1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + "任务完成!✅");
}
}
// 使用线程(养育孩子)
public class ThreadTest {
public static void main(String[] args) {
// 创建线程对象
MyThread t1 = new MyThread("线程1");
MyThread t2 = new MyThread("线程2");
// 启动线程(注意:是start()不是run()!)
t1.start(); // ✅ 正确:开启新线程
t2.start(); // ✅ 正确:开启新线程
// ❌ 错误示范:
// t1.run(); // 这只是普通方法调用,不会开新线程!
System.out.println("主线程继续干自己的事... 💼");
}
}
运行结果(顺序可能不同,这就是多线程的魅力):
主线程继续干自己的事... 💼
线程1正在执行第1次任务 🏃
线程2正在执行第1次任务 🏃
线程1正在执行第2次任务 🏃
线程2正在执行第2次任务 🏃
...
优缺点:
| 优点 👍 | 缺点 👎 |
|---|---|
| 代码简单,容易理解 | 已经继承了Thread,不能再继承别的类了 |
| 直接使用Thread类的方法 | 不够灵活,扩展性差 |
| 适合简单场景 | 不符合面向接口编程思想 |
方式2️⃣:实现Runnable接口(温柔贤惠)✅ 推荐!
特点:灵活优雅,可以继承别的类,符合设计模式
// 定义一个任务类(就像写了个任务清单)
class MyRunnable implements Runnable {
private String name;
private int count;
public MyRunnable(String name, int count) {
this.name = name;
this.count = count;
}
@Override
public void run() {
for (int i = 1; i <= count; i++) {
System.out.println(name + "正在执行第" + i + "次任务 🎯");
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name + "完成所有任务!🎉");
}
}
// 使用方式
public class RunnableTest {
public static void main(String[] args) {
// 创建任务对象
MyRunnable task1 = new MyRunnable("任务A", 3);
MyRunnable task2 = new MyRunnable("任务B", 3);
// 创建线程,把任务交给线程
Thread t1 = new Thread(task1);
Thread t2 = new Thread(task2);
// 启动线程
t1.start();
t2.start();
System.out.println("主线程:我派出了两个线程去干活,我继续摸鱼... 🐟");
}
}
优缺点:
| 优点 👍 | 缺点 👎 |
|---|---|
| 可以继承其他类,灵活性强 | 代码稍微复杂一点点 |
| 多个线程可以共享同一个Runnable对象 | 需要多写一行代码(创建Thread对象) |
| 符合面向接口编程思想 | (其实没啥缺点,业界推荐!) |
| 线程池只接受Runnable/Callable |
方式3️⃣:实现Callable接口(高配版)🌟
特点:可以有返回值,可以抛异常,功能最强大!
import java.util.concurrent.*;
// 定义一个有返回值的任务
class MyCallable implements Callable<Integer> {
private int num;
public MyCallable(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
System.out.println("开始计算1到" + num + "的和... 🧮");
int sum = 0;
for (int i = 1; i <= num; i++) {
sum += i;
Thread.sleep(100); // 模拟计算耗时
}
System.out.println("计算完成!结果是:" + sum + " ✅");
return sum;
}
}
// 使用方式
public class CallableTest {
public static void main(String[] args) throws Exception {
// 创建任务
MyCallable task = new MyCallable(100);
// 创建FutureTask(包装器)
FutureTask<Integer> futureTask = new FutureTask<>(task);
// 创建线程并启动
Thread t = new Thread(futureTask);
t.start();
System.out.println("主线程:我让子线程去算1+2+...+100,我先干点别的... 📝");
// 模拟主线程干其他事
Thread.sleep(2000);
System.out.println("主线程:现在我要获取结果了... 🔍");
// 获取返回值(会阻塞,直到计算完成)
Integer result = futureTask.get();
System.out.println("主线程:拿到结果了!答案是:" + result + " 🎊");
}
}
三种方式对比:
┌──────────────┬───────────┬───────────┬────────────┐
│ 对比项 │ Thread │ Runnable │ Callable │
├──────────────┼───────────┼───────────┼────────────┤
│ 难度 │ ⭐ │ ⭐⭐ │ ⭐⭐⭐ │
│ 灵活性 │ 低 ❌ │ 高 ✅ │ 高 ✅ │
│ 返回值 │ 无 ❌ │ 无 ❌ │ 有 ✅ │
│ 异常处理 │ 差 😐 │ 差 😐 │ 好 😊 │
│ 推荐度 │ ⭐⭐ │ ⭐⭐⭐⭐ │ ⭐⭐⭐⭐⭐ │
│ 使用场景 │ 简单任务 │ 常规任务 │ 需要结果 │
└──────────────┴───────────┴───────────┴────────────┘
🎯 初学者建议:从Runnable学起
💼 工作实战:优先用Callable和线程池
🌱 线程的一生:从出生到退休
线程的六个人生阶段
线程的一生(就像人生一样)
1️⃣ 新建状态 (NEW) - 刚出生的婴儿 👶
↓ start()
2️⃣ 就绪状态 (RUNNABLE) - 等待上场的运动员 🏃♂️
↓ CPU调度
3️⃣ 运行状态 (RUNNING) - 正在工作的打工人 💼
↓
├→ sleep()/wait() → 4️⃣ 阻塞/等待状态 (BLOCKED/WAITING)
├→ 等待锁 → 5️⃣ 超时等待 (TIMED_WAITING)
└→ run()执行完 → 6️⃣ 终止状态 (TERMINATED) - 退休 🎉
详细生命周期图解
线程状态转换图
NEW (新建)
|
| start() - 线程启动!
↓
RUNNABLE (就绪)
|
| CPU分配时间片
↓
RUNNING (运行)
|
├──→ sleep(1000) ──→ TIMED_WAITING (定时等待) ──┐
| "我睡会儿,1秒后叫我" |
| |
├──→ wait() ──→ WAITING (等待) |
| "有人叫我我才醒" |
| ↓ |
| notify()/notifyAll() |
| ↓ |
├──→ 等待锁 ──→ BLOCKED (阻塞) ──────────────────┤
| "排队等锁,前面的人太慢了😭" |
| |
└──→ run()执行完 ──→ TERMINATED (终止) |
"功成身退,退休啦!🎉" |
|
←──────────────────────────────────────────────┘
回到RUNNABLE状态
生活化理解
1️⃣ NEW (新建) - 刚出生 👶
Thread t = new Thread(() -> {
System.out.println("我是新线程!");
});
// 此时线程已创建,但还没启动
// 就像婴儿刚出生,还没开始活动
2️⃣ RUNNABLE (就绪) - 准备就绪 🏃♂️
t.start(); // 启动线程
// 线程进入就绪状态,等待CPU调度
// 就像运动员站在起跑线,等待发令枪
比喻:
你在银行排队办业务:
- 取了号(NEW)
- 在等候区坐着,等叫号(RUNNABLE)
- 轮到你了,去柜台办理(RUNNING)
3️⃣ RUNNING (运行) - 努力工作 💼
// run()方法正在执行
// CPU正在执行这个线程的代码
// 就像你正在专心工作
4️⃣ BLOCKED (阻塞) - 排队等待 😤
synchronized(lock) {
// 如果lock被别的线程占用
// 当前线程就会进入BLOCKED状态
// 就像上厕所发现有人,只能在门口等
}
比喻:
厕所门口排队:
前面有人在用 → BLOCKED状态 😭
前面的人出来了 → 回到RUNNABLE → 轮到你了 → RUNNING 🎉
5️⃣ WAITING/TIMED_WAITING (等待) - 休息一下 😴
// TIMED_WAITING (定时等待)
Thread.sleep(1000); // 睡1秒,1秒后自动醒
// 就像设了闹钟的午休
// WAITING (无限等待)
Object.wait(); // 一直等,直到有人叫醒
// 就像等快递,不知道啥时候来
比喻:
TIMED_WAITING(定时等待):
你:我眯一会儿,30分钟后叫我 ⏰
→ 30分钟后闹钟响,自动醒来
WAITING(无限等待):
你:等快递到了叫我 📦
→ 快递员打电话,你才醒(notify)
6️⃣ TERMINATED (终止) - 任务完成 🎉
// run()方法执行完毕,或者出现异常
// 线程生命周期结束,无法重启
// 就像退休了,不能再回去工作
🎮 状态转换代码演示
public class ThreadLifeCycle {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("1. 线程开始运行 (RUNNING) 🏃");
try {
System.out.println("2. 准备睡觉2秒 (TIMED_WAITING) 😴");
Thread.sleep(2000);
System.out.println("3. 睡醒了,继续干活 (RUNNING) 💪");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4. 任务完成,准备退休 (TERMINATED) 🎉");
});
System.out.println("线程状态: " + t.getState()); // NEW
t.start();
System.out.println("线程状态: " + t.getState()); // RUNNABLE
Thread.sleep(500);
System.out.println("线程状态: " + t.getState()); // TIMED_WAITING
Thread.sleep(2000);
System.out.println("线程状态: " + t.getState()); // TERMINATED
}
}
🔒 线程同步:别抢了,排队!
🎭 问题场景:银行取钱的悲剧
场景:你和你老婆同时去ATM取钱
银行账户:1000元
【没有同步的悲剧】:
你:查询余额 → 1000元 → 取800元
老婆:查询余额 → 1000元 → 取500元(同时进行)
结果:
你取走800,余额应该是200
老婆取走500,余额应该是-300???💥
银行:???账炸了!🤯
代码演示问题:
// 银行账户类(不安全版本)❌
class BankAccount {
private int balance = 1000; // 余额1000元
// 取钱方法(不安全!)
public void withdraw(String name, int amount) {
// 检查余额
if (balance >= amount) {
System.out.println(name + "查询余额:" + balance + "元 ✅");
// 模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 取钱
balance -= amount;
System.out.println(name + "取走" + amount + "元,剩余:" + balance + "元 💰");
} else {
System.out.println(name + "余额不足!❌");
}
}
}
// 测试
public class UnsafeWithdraw {
public static void main(String[] args) {
BankAccount account = new BankAccount();
// 你和老婆同时取钱
Thread you = new Thread(() -> {
account.withdraw("你", 800);
});
Thread wife = new Thread(() -> {
account.withdraw("老婆", 500);
});
you.start();
wife.start();
}
}
运行结果(悲剧):
你查询余额:1000元 ✅
老婆查询余额:1000元 ✅
你取走800元,剩余:200元 💰
老婆取走500元,剩余:-300元 💰 ← 炸了!💥
🔒 解决方案1:synchronized关键字(最常用)
比喻:给厕所装把锁🚪🔒
没锁的厕所:
你进去了,别人也能进来 → 尴尬!😱
有锁的厕所:
你进去锁门 → 别人只能在外面等 → 你出来开锁 → 下一个进去
代码修复(安全版本):✅
// 银行账户类(安全版本)
class SafeBankAccount {
private int balance = 1000;
// 方式1:同步方法(推荐)
public synchronized void withdraw(String name, int amount) {
if (balance >= amount) {
System.out.println(name + "查询余额:" + balance + "元 ✅");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= amount;
System.out.println(name + "取走" + amount + "元,剩余:" + balance + "元 💰");
} else {
System.out.println(name + "余额不足!❌");
}
}
}
运行结果(正常):
你查询余额:1000元 ✅
你取走800元,剩余:200元 💰
老婆查询余额:200元 ✅
老婆余额不足!❌ ← 正确!✅
🔒 synchronized的三种用法
1️⃣ 同步方法(锁的是this对象)
public synchronized void method() {
// 同一时间只有一个线程能进来
// 就像单人厕所🚻
}
2️⃣ 同步代码块(锁指定对象)
public void method() {
synchronized(this) {
// 只有这部分代码是同步的
// 就像厕所里只有马桶区域上锁
}
}
3️⃣ 同步静态方法(锁的是类.class对象)
public static synchronized void method() {
// 锁的是整个类
// 就像整栋楼只有一个厕所
}
🎯 synchronized实战案例:电影院售票系统
class TicketOffice {
private int tickets = 10; // 总共10张票
// 同步售票方法
public synchronized void sellTicket(String window) {
if (tickets > 0) {
System.out.println(window + "售出第" + tickets + "张票 🎫");
tickets--;
System.out.println("剩余票数:" + tickets);
} else {
System.out.println(window + "票已售罄!❌");
}
}
}
public class TicketTest {
public static void main(String[] args) {
TicketOffice office = new TicketOffice();
// 3个售票窗口(3个线程)
Thread window1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
office.sellTicket("窗口1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
});
Thread window2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
office.sellTicket("窗口2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
});
Thread window3 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
office.sellTicket("窗口3");
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
});
window1.start();
window2.start();
window3.start();
}
}
🔐 解决方案2:Lock锁(更灵活)
比喻:synchronized是自动门锁,Lock是手动门锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class AdvancedBankAccount {
private int balance = 1000;
private Lock lock = new ReentrantLock(); // 创建一把锁
public void withdraw(String name, int amount) {
lock.lock(); // 手动上锁 🔒
try {
if (balance >= amount) {
System.out.println(name + "取走" + amount + "元");
balance -= amount;
System.out.println("剩余:" + balance + "元");
} else {
System.out.println(name + "余额不足!");
}
} finally {
lock.unlock(); // 手动解锁 🔓(一定要在finally里!)
}
}
}
synchronized vs Lock 对比
┌─────────────┬──────────────────┬──────────────────┐
│ 对比项 │ synchronized │ Lock │
├─────────────┼──────────────────┼──────────────────┤
│ 使用难度 │ 简单 😊 │ 稍复杂 🤔 │
│ 锁的释放 │ 自动释放 ✅ │ 手动释放 ⚠️ │
│ 灵活性 │ 低 😐 │ 高 😎 │
│ 性能 │ 一般 ⭐⭐⭐ │ 更好 ⭐⭐⭐⭐ │
│ 公平锁 │ 非公平 ❌ │ 可选公平/非公平 ✅│
│ 尝试加锁 │ 不支持 ❌ │ 支持tryLock() ✅ │
│ 超时加锁 │ 不支持 ❌ │ 支持 ✅ │
│ 推荐场景 │ 简单同步 │ 复杂并发控制 │
└─────────────┴──────────────────┴──────────────────┘
🎯 建议:
- 初学者:先用synchronized,简单好理解
- 进阶者:需要高级功能时用Lock
⚠️ 死锁:线程界的"相互拉黑" 💔
比喻:
你和朋友吵架了:
你:除非你先道歉,否则我不理你!
朋友:除非你先道歉,否则我不理你!
结果:两个人永远僵持,谁也不理谁 😭
这就是死锁!
代码示例(死锁):❌
public class DeadLockDemo {
static Object lockA = new Object();
static Object lockB = new Object();
public static void main(String[] args) {
// 线程1:先锁A,再锁B
Thread t1 = new Thread(() -> {
synchronized(lockA) {
System.out.println("线程1:我拿到了A锁 🔒");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("线程1:我想要B锁... 🤔");
synchronized(lockB) {
System.out.println("线程1:我拿到了B锁!✅");
}
}
});
// 线程2:先锁B,再锁A
Thread t2 = new Thread(() -> {
synchronized(lockB) {
System.out.println("线程2:我拿到了B锁 🔒");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("线程2:我想要A锁... 🤔");
synchronized(lockA) {
System.out.println("线程2:我拿到了A锁!✅");
}
}
});
t1.start();
t2.start();
}
}
运行结果:
线程1:我拿到了A锁 🔒
线程2:我拿到了B锁 🔒
线程1:我想要B锁... 🤔
线程2:我想要A锁... 🤔
(程序卡死,永远等待)💀
解决死锁的方法:
1️⃣ 统一加锁顺序
所有线程都按:先锁A,再锁B
2️⃣ 使用tryLock()设置超时
等不到就放弃,不死等
3️⃣ 避免嵌套锁
尽量不要在一个锁里面再要另一个锁
4️⃣ 使用Lock的tryLock()
lock.tryLock(1, TimeUnit.SECONDS)
等1秒,拿不到锁就放弃
🏊 线程池:线程界的劳务派遣公司
🤔 为什么要用线程池?
没有线程池的公司(悲剧):
老板:来了一个任务,招一个员工!
任务做完,员工走人!
又来一个任务,再招一个员工!
...
结果:
💰 招聘成本巨高(频繁创建销毁线程)
😰 效率低下(创建线程很耗时)
🤯 资源浪费(线程太多,系统崩溃)
有线程池的公司(完美):
老板:提前招10个固定员工(线程池)
有任务就分配给空闲员工
做完继续待命,不用辞退
结果:
💰 成本低(线程复用,不用反复创建)
⚡ 效率高(线程已经准备好了)
😊 稳定性好(线程数量可控)
🏊 线程池的四种类型
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1️⃣ 固定大小线程池(最常用)
ExecutorService fixedPool = Executors.newFixedThreadPool(5);
// 就像:公司固定5个员工,不多不少
// 2️⃣ 单线程池
ExecutorService singlePool = Executors.newSingleThreadExecutor();
// 就像:公司只有1个员工,任务排队做
// 3️⃣ 缓存线程池
ExecutorService cachedPool = Executors.newCachedThreadPool();
// 就像:任务多就多招人,任务少就裁员(60秒没活干就走人)
// 4️⃣ 定时任务线程池
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(3);
// 就像:专门做定时任务的员工(每天9点打卡)
}
}
🎯 线程池实战:批量处理订单
import java.util.concurrent.*;
public class OrderProcessor {
public static void main(String[] args) {
// 创建固定5个线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
System.out.println("双十一来了!开始处理订单... 🛒");
// 模拟100个订单
for (int i = 1; i <= 100; i++) {
final int orderNo = i;
// 提交任务给线程池
pool.execute(() -> {
System.out.println(Thread.currentThread().getName() +
" 正在处理订单" + orderNo + " 📦");
try {
Thread.sleep(100); // 模拟处理耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单" + orderNo + "处理完成!✅");
});
}
System.out.println("所有订单已分配给线程池!");
// 关闭线程池(不再接受新任务,但会把现有任务做完)
pool.shutdown();
System.out.println("等待所有订单处理完成... ⏳");
}
}
运行效果:
双十一来了!开始处理订单... 🛒
所有订单已分配给线程池!
等待所有订单处理完成... ⏳
pool-1-thread-1 正在处理订单1 📦
pool-1-thread-2 正在处理订单2 📦
pool-1-thread-3 正在处理订单3 📦
pool-1-thread-4 正在处理订单4 📦
pool-1-thread-5 正在处理订单5 📦
订单1处理完成!✅
pool-1-thread-1 正在处理订单6 📦
...
(5个线程不停地处理100个订单)
🎯 定时任务线程池:每天自动打卡
import java.util.concurrent.*;
public class ScheduledTaskDemo {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
System.out.println("设置定时任务... ⏰");
// 1️⃣ 延迟执行:3秒后执行一次
scheduler.schedule(() -> {
System.out.println("【延迟任务】3秒后执行了!⏱️");
}, 3, TimeUnit.SECONDS);
// 2️⃣ 固定频率执行:1秒后开始,每2秒执行一次
scheduler.scheduleAtFixedRate(() -> {
System.out.println("【固定频率】每2秒执行一次!🔄 " +
System.currentTimeMillis());
}, 1, 2, TimeUnit.SECONDS);
// 3️⃣ 固定延迟执行:任务结束后,等3秒再执行下一次
scheduler.scheduleWithFixedDelay(() -> {
System.out.println("【固定延迟】做完等3秒再来!💤");
try {
Thread.sleep(1000); // 模拟任务耗时1秒
} catch (InterruptedException e) {}
}, 1, 3, TimeUnit.SECONDS);
// 10秒后关闭
scheduler.schedule(() -> {
System.out.println("时间到,关闭线程池!👋");
scheduler.shutdown();
}, 10, TimeUnit.SECONDS);
}
}
📊 线程池核心参数(面试必问!)
// ⚠️ 不推荐用Executors创建,推荐自己定义参数!
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5, // corePoolSize: 核心线程数(正式员工)
10, // maximumPoolSize: 最大线程数(正式+临时工)
60, // keepAliveTime: 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(100), // 任务队列(待办事项列表)
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
形象理解:
🏢 一家公司的用人策略:
1️⃣ 核心线程数(corePoolSize = 5):
正式员工5人,永远不辞退
2️⃣ 任务队列(capacity = 100):
待办事项清单最多100个任务
3️⃣ 最大线程数(maximumPoolSize = 10):
任务太多了,再招5个临时工(最多10人)
4️⃣ 空闲存活时间(keepAliveTime = 60秒):
临时工60秒没活干就走人
5️⃣ 拒绝策略:
任务太多了(>10人+100任务),怎么办?
- AbortPolicy: 直接拒绝,抛异常 ❌
- CallerRunsPolicy: 谁叫我干的,谁自己干!
- DiscardPolicy: 悄悄扔掉,不管了 🗑️
- DiscardOldestPolicy: 扔掉最老的任务
执行流程:
新任务来了!
↓
核心线程都在忙吗?
/ \
不忙 忙
↓ ↓
分配给 任务队列满了吗?
核心线程 / \
没满 满了
↓ ↓
放队列 线程数<最大值吗?
/ \
是 否
↓ ↓
创建临时线程 执行拒绝策略!
⚠️ 常见坑点:别踩雷!
坑1️⃣:start() vs run()
Thread t = new Thread(() -> {
System.out.println("我是子线程:" + Thread.currentThread().getName());
});
// ❌ 错误:直接调用run()
t.run(); // 输出:我是子线程:main(没有开新线程!)
// ✅ 正确:调用start()
t.start(); // 输出:我是子线程:Thread-0(开了新线程!)
比喻:
run() = 你自己洗碗(在主线程里执行)
start() = 叫保姆洗碗(开新线程执行)
坑2️⃣:线程不能重复start()
Thread t = new Thread(() -> {
System.out.println("任务执行");
});
t.start(); // ✅ 第一次可以
t.start(); // ❌ 第二次报错:IllegalThreadStateException
// 解决方法:创建新线程
Thread t2 = new Thread(() -> {
System.out.println("任务执行");
});
t2.start(); // ✅ OK
比喻:
线程就像一次性筷子🥢
用过了就不能再用,要再用就拿新的
坑3️⃣:忘记关闭线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
pool.execute(() -> System.out.println("任务执行"));
}
// ❌ 没有关闭线程池,程序不会退出!
// pool.shutdown(); // ✅ 正确:关闭线程池
比喻:
不关线程池 = 下班了不关灯不锁门
结果:电费一直烧💸,小偷进来💰
坑4️⃣:sleep不释放锁
synchronized(lock) {
Thread.sleep(1000); // ❌ 睡觉时还抓着锁不放!
// 别的线程只能干等着 😭
}
// ✅ 正确做法:
synchronized(lock) {
lock.wait(1000); // 睡觉时释放锁,让别人先用
}
比喻:
sleep() = 在厕所里睡觉,门还锁着(别人进不来)😭
wait() = 出来让别人先用,别人叫你再进去 😊
坑5️⃣:并发修改异常
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
// ❌ 错误:边遍历边删除
for (String item : list) {
list.remove(item); // 报错:ConcurrentModificationException
}
// ✅ 正确:用迭代器
Iterator<String> it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove(); // ✅ OK
}
// ✅ 或者用线程安全的集合
List<String> safeList = new CopyOnWriteArrayList<>();
🎯 实战演练:手把手教学
案例1:多线程下载器 📥
import java.util.concurrent.*;
class FileDownloader {
// 模拟下载一个文件块
static class DownloadTask implements Runnable {
private String fileName;
private int partNo;
public DownloadTask(String fileName, int partNo) {
this.fileName = fileName;
this.partNo = partNo;
}
@Override
public void run() {
System.out.println("开始下载:" + fileName + " 第" + partNo + "部分 📥");
try {
// 模拟下载耗时
Thread.sleep((int)(Math.random() * 2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成下载:" + fileName + " 第" + partNo + "部分 ✅");
}
}
public static void main(String[] args) throws InterruptedException {
String fileName = "Java从入门到放弃.pdf";
int parts = 10; // 分10个部分下载
ExecutorService pool = Executors.newFixedThreadPool(3);
System.out.println("开始多线程下载:" + fileName);
System.out.println("文件分为" + parts + "个部分,使用3个线程下载");
System.out.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━");
long startTime = System.currentTimeMillis();
for (int i = 1; i <= parts; i++) {
pool.execute(new DownloadTask(fileName, i));
}
pool.shutdown();
pool.awaitTermination(1, TimeUnit.MINUTES);
long endTime = System.currentTimeMillis();
System.out.println("━━━━━━━━━━━━━━━━━━━━━━━━━━━");
System.out.println("下载完成!耗时:" + (endTime - startTime) + "ms 🎉");
}
}
案例2:生产者-消费者模式 🏭
import java.util.LinkedList;
import java.util.Queue;
class Factory {
private Queue<String> products = new LinkedList<>();
private int maxSize = 5; // 仓库最多5个产品
// 生产者
public synchronized void produce(String product) throws InterruptedException {
while (products.size() >= maxSize) {
System.out.println("仓库满了,生产者等待... 😴");
wait(); // 仓库满了,等待消费
}
products.offer(product);
System.out.println("生产了:" + product + ",库存:" + products.size() + " 📦");
notifyAll(); // 通知消费者来消费
}
// 消费者
public synchronized String consume() throws InterruptedException {
while (products.isEmpty()) {
System.out.println("仓库空了,消费者等待... 😴");
wait(); // 仓库空了,等待生产
}
String product = products.poll();
System.out.println("消费了:" + product + ",库存:" + products.size() + " 🛒");
notifyAll(); // 通知生产者来生产
return product;
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
Factory factory = new Factory();
// 生产者线程
Thread producer = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
factory.produce("产品" + i);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "生产者");
// 消费者线程
Thread consumer = new Thread(() -> {
for (int i = 1; i <= 10; i++) {
try {
factory.consume();
Thread.sleep(1000); // 消费慢一点
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者");
producer.start();
consumer.start();
}
}
案例3:多线程计数器(Atomic原子类)⚛️
import java.util.concurrent.atomic.AtomicInteger;
public class CounterDemo {
// ❌ 不安全的计数器
static class UnsafeCounter {
private int count = 0;
public void increment() {
count++; // 不是原子操作!
}
public int getCount() {
return count;
}
}
// ✅ 安全的计数器(使用Atomic)
static class SafeCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子操作!
}
public int getCount() {
return count.get();
}
}
public static void main(String[] args) throws InterruptedException {
// 测试不安全的计数器
UnsafeCounter unsafeCounter = new UnsafeCounter();
SafeCounter safeCounter = new SafeCounter();
// 10个线程,每个线程+1000次
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
unsafeCounter.increment();
safeCounter.increment();
}
});
threads[i].start();
}
// 等待所有线程完成
for (Thread t : threads) {
t.join();
}
System.out.println("期望结果:10000");
System.out.println("不安全计数器:" + unsafeCounter.getCount() + " ❌");
System.out.println("安全计数器:" + safeCounter.getCount() + " ✅");
}
}
🚀 高级技巧:从青铜到王者
技巧1️⃣:使用ThreadLocal避免线程冲突
比喻:每个人有自己的钱包,不用共享💰
public class ThreadLocalDemo {
// 每个线程有自己的副本
static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
final int userId = i;
new Thread(() -> {
// 设置当前线程的值
threadLocal.set(userId);
System.out.println(Thread.currentThread().getName() +
" 的用户ID:" + threadLocal.get());
// 不会影响其他线程的值!
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() +
" 再次读取ID:" + threadLocal.get());
// 用完记得清理
threadLocal.remove();
}, "线程" + i).start();
}
}
}
技巧2️⃣:使用CountDownLatch等待所有线程完成
比喻:等所有人到齐了再开会🚦
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int playerCount = 5;
CountDownLatch latch = new CountDownLatch(playerCount);
System.out.println("游戏大厅:等待5个玩家进入... 🎮");
for (int i = 1; i <= playerCount; i++) {
final int no = i;
new Thread(() -> {
try {
Thread.sleep((int)(Math.random() * 3000));
System.out.println("玩家" + no + "已进入!");
latch.countDown(); // 计数器-1
} catch (InterruptedException e) {}
}).start();
}
latch.await(); // 等待计数器归零
System.out.println("所有玩家已到齐,游戏开始!🎉");
}
}
技巧3️⃣:使用CyclicBarrier实现赛跑
比喻:所有人到起跑线,一起出发!🏃
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
int runnerCount = 5;
CyclicBarrier barrier = new CyclicBarrier(runnerCount, () -> {
System.out.println("所有运动员就位,预备... 开跑!🏁");
});
for (int i = 1; i <= runnerCount; i++) {
final int no = i;
new Thread(() -> {
try {
System.out.println("运动员" + no + "到达起跑线");
barrier.await(); // 等待其他运动员
// 所有人都到了,开始跑
System.out.println("运动员" + no + "开始跑!🏃");
} catch (Exception e) {}
}).start();
}
}
}
技巧4️⃣:使用Semaphore限流
比喻:停车场只有3个车位🅿️
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore parking = new Semaphore(3); // 3个车位
for (int i = 1; i <= 10; i++) {
final int carNo = i;
new Thread(() -> {
try {
System.out.println("车" + carNo + "到达停车场... 🚗");
parking.acquire(); // 获取车位(如果满了就等)
System.out.println("车" + carNo + "停进车位!🅿️");
Thread.sleep(2000); // 停2秒
System.out.println("车" + carNo + "离开车位!👋");
parking.release(); // 释放车位
} catch (InterruptedException e) {}
}).start();
}
}
}
📊 多线程性能对比
实验:计算1到1亿的和
public class PerformanceTest {
static final int MAX = 100_000_000;
// 单线程计算
static long singleThread() {
long start = System.currentTimeMillis();
long sum = 0;
for (int i = 1; i <= MAX; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("单线程:" + (end - start) + "ms,结果:" + sum);
return sum;
}
// 多线程计算(4个线程)
static long multiThread() throws Exception {
long start = System.currentTimeMillis();
int threadCount = 4;
ExecutorService pool = Executors.newFixedThreadPool(threadCount);
final long[] results = new long[threadCount];
CountDownLatch latch = new CountDownLatch(threadCount);
int range = MAX / threadCount;
for (int i = 0; i < threadCount; i++) {
final int index = i;
final int startNum = i * range + 1;
final int endNum = (i + 1) * range;
pool.execute(() -> {
long partSum = 0;
for (int j = startNum; j <= endNum; j++) {
partSum += j;
}
results[index] = partSum;
latch.countDown();
});
}
latch.await();
pool.shutdown();
long sum = 0;
for (long result : results) {
sum += result;
}
long end = System.currentTimeMillis();
System.out.println("多线程:" + (end - start) + "ms,结果:" + sum);
return sum;
}
public static void main(String[] args) throws Exception {
System.out.println("计算1到1亿的和");
System.out.println("━━━━━━━━━━━━━━━━━━━━");
singleThread();
multiThread();
System.out.println("━━━━━━━━━━━━━━━━━━━━");
System.out.println("多线程快了好几倍!⚡");
}
}
运行结果:
计算1到1亿的和
━━━━━━━━━━━━━━━━━━━━
单线程:156ms,结果:5000000050000000
多线程:48ms,结果:5000000050000000
━━━━━━━━━━━━━━━━━━━━
多线程快了3倍多!⚡
🎓 面试高频问题
Q1:线程和进程的区别?
进程 = 一个公司 🏢
线程 = 公司里的员工 👨💼
区别:
1️⃣ 进程是资源分配的最小单位
线程是CPU调度的最小单位
2️⃣ 进程有独立的内存空间
同一进程的线程共享内存
3️⃣ 进程间通信困难(要通过IPC)
线程间通信简单(共享变量)
4️⃣ 进程切换开销大 💰
线程切换开销小 💵
5️⃣ 进程崩溃不影响其他进程
线程崩溃会导致整个进程挂掉 💥
Q2:synchronized和volatile的区别?
synchronized = 上锁的保险箱 🔒
- 保证原子性:同一时间只有一个线程能访问
- 保证可见性:修改对其他线程立即可见
- 保证有序性:禁止指令重排序
- 性能开销较大
volatile = 透明玻璃箱 📦
- 不保证原子性:count++不是原子操作
- 保证可见性:修改立即刷新到主内存
- 保证有序性:禁止指令重排序
- 性能开销较小
使用场景:
volatile:标志位、状态值(读多写少)
synchronized:复杂的原子操作、临界区
Q3:如何避免死锁?
1️⃣ 统一加锁顺序
所有线程都按相同顺序获取锁
2️⃣ 设置超时时间
tryLock(timeout),等不到就放弃
3️⃣ 避免嵌套锁
不要在持有锁A时去申请锁B
4️⃣ 使用并发工具类
用Lock、Semaphore等替代synchronized
5️⃣ 死锁检测
定期检测,发现后回滚
🎉 总结:多线程知识图谱
╔════════════════════════════════════════════════════════╗
║ 🎓 Java多线程知识体系 🎓 ║
╠════════════════════════════════════════════════════════╣
║ ║
║ 基础篇 ║
║ ├─ Thread vs Runnable vs Callable ║
║ ├─ 线程生命周期(6个状态) ║
║ ├─ start() vs run() ║
║ └─ sleep() vs wait() ║
║ ║
║ 同步篇 ║
║ ├─ synchronized(同步方法/代码块) ║
║ ├─ Lock(ReentrantLock) ║
║ ├─ volatile(可见性) ║
║ ├─ Atomic(原子类) ║
║ └─ 死锁(原因与预防) ║
║ ║
║ 线程池篇 ║
║ ├─ FixedThreadPool(固定大小) ║
║ ├─ CachedThreadPool(缓存型) ║
║ ├─ SingleThreadExecutor(单线程) ║
║ ├─ ScheduledThreadPool(定时任务) ║
║ └─ ThreadPoolExecutor(自定义) ║
║ ║
║ 高级篇 ║
║ ├─ ThreadLocal(线程本地变量) ║
║ ├─ CountDownLatch(倒计时门栓) ║
║ ├─ CyclicBarrier(循环栅栏) ║
║ ├─ Semaphore(信号量) ║
║ └─ 并发集合(ConcurrentHashMap等) ║
║ ║
╚════════════════════════════════════════════════════════╝
💡 学习路线图
🗺️ Java多线程修炼之路
第1周:入门阶段 🌱
✅ 理解多线程概念
✅ 掌握Thread和Runnable
✅ 学会启动和停止线程
🎯 目标:能写出简单的多线程程序
第2周:进阶阶段 🌿
✅ 理解线程同步
✅ 掌握synchronized和Lock
✅ 学习线程通信(wait/notify)
🎯 目标:能处理线程安全问题
第3周:实战阶段 🌳
✅ 掌握线程池使用
✅ 学习Callable和Future
✅ 实战:多线程下载器、生产者消费者
🎯 目标:能独立完成项目
第4周:高级阶段 🌲
✅ 掌握并发工具类
✅ 学习JUC包(java.util.concurrent)
✅ 理解并发原理(JMM、CAS)
🎯 目标:能应对面试和复杂场景
记住:多线程就像学开车 🚗
- 先理论(理解概念)
- 再实践(写代码)
- 多练习(做项目)
- 终成神(解决实际问题)
📚 推荐资源
📖 书籍:
1. 《Java并发编程实战》(圣经级别)
2. 《Java多线程编程核心技术》(入门友好)
3. 《深入理解Java虚拟机》(深入原理)
🎥 视频:
1. 尚硅谷JUC并发编程(B站免费)
2. 黑马程序员多线程教程(小白友好)
🌐 网站:
1. Java官方文档
2. 并发编程网
3. StackOverflow(遇到问题必查)
💼 实战项目:
1. 多线程爬虫
2. 聊天室服务器
3. 文件下载器
4. 订单处理系统
🎊 彩蛋:多线程趣味小知识
🤯 你知道吗?
1. 程序员的咖啡因多线程
单线程程序员:喝一杯咖啡,写一段代码,睡一觉
多线程程序员:喝咖啡同时写代码,写代码同时debug,
debug同时修bug,修bug同时摸鱼 😂
2. 多线程的bug最难找
为什么?
- 有时候能复现,有时候不能(看心情)
- 本地没问题,服务器就出问题(神秘)
- 加个打印语句,bug竟然消失了(见鬼)
- 删掉打印语句,bug又回来了(闹鬼)
原因:多线程有不确定性(时序问题)
解决:多打日志、用同步工具、祈祷🙏
3. 线程数不是越多越好
误区:100个线程 > 10个线程?
真相:CPU只有4核,100个线程反而更慢!
原因:
- 线程切换开销大(上下文切换)
- 内存消耗大(每个线程1MB栈空间)
- 锁竞争激烈(100个人抢4个坑)
最佳实践:
CPU密集型:线程数 = CPU核心数 + 1
IO密集型:线程数 = CPU核心数 × 2
4. Java的Thread-0从哪来的?
// 默认线程名:Thread-0, Thread-1, Thread-2...
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});
t.start(); // 输出:Thread-0
// 可以自定义名字(便于调试)
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
}, "我的线程");
t2.start(); // 输出:我的线程
建议:生产环境一定要给线程起个好名字!
否则出了问题,你都不知道是哪个线程炸了 💥
🎮 趣味小测试
测试:你的多线程段位?
1. 你能说出线程的6个状态吗?
能 → +20分
不能 → 回去复习!
2. 你知道start()和run()的区别吗?
知道 → +20分
不知道 → 青铜玩家
3. 你会用synchronized吗?
会 → +20分
不会 → 还在新手村
4. 你用过线程池吗?
用过 → +20分
没用过 → 白银水平
5. 你能手写生产者消费者吗?
能 → +20分
不能 → 黄金水平
分数评估:
100分 → 王者 👑(面试官都怕你)
80分 → 钻石 💎(能独当一面)
60分 → 黄金 🏅(能解决大部分问题)
40分 → 白银 ⚪(入门了)
20分 → 青铜 🥉(继续加油)
0分 → 黑铁 ⚫(从头学起)
不管多少分,学就对了!💪
✍️ 作者的话
恭喜你看完这篇超级长的多线程教程!🎊
写这篇文档时,我的电脑开了32个线程(Chrome浏览器🌐), 喝了8杯咖啡☕,吃了5包薯片🥔, 线程池爆了3次💥,死锁调试了2小时😭。
但这一切都是值得的,因为你学会了多线程! 🎉
记住:
"单线程就像单身,多线程就像恋爱。 单身简单但孤单,恋爱复杂但精彩!" 💑
学习多线程不是为了炫技,而是为了让程序跑得更快、更稳! 💪
现在,关掉这个文档,去写几个多线程程序练练手吧!
Happy Coding! 🚀
版本: v1.0
最后更新: 2025年10月
作者: Java修炼爱好者 😊
许可: 自由传播,注明出处即可
🎉 THE END 🎉
_______ _ _ _
|__ __| | | | |
| | | |__ _ __ ___ __ _ __| | |
| | | '_ \| '__/ _ \/ _` |/ _` | |
| | | | | | | | __/ (_| | (_| |_|
|_| |_| |_|_| \___|\__,_|\__,_(_)
🎓 祝你在多线程的世界里玩得开心!🚀
P.S. 如果你真的看到这里了,给自己点个赞吧!👍
现在,去实践吧!多线程的世界等着你!
Thread.start() your journey! 🏃♂️💨
记住:
while (true) {
学习();
实践();
debug();
if (成功) {
庆祝();
continue;
} else {
反思();
重来();
}
}
永不放弃,直到成功! 💪🔥