🎮 Java多线程:让你的程序从"单核土豆"变成"多核战神"!🚀

23 阅读28分钟

作者按:本文保证让你笑着学会多线程!如果看完还不懂,那一定是我写得不够好笑!😎


📚 目录

  1. 开篇:多线程是个啥玩意儿?
  2. 生活中的多线程:你早就在用了!
  3. 线程家族谱:Thread和Runnable的爱恨情仇
  4. 线程的一生:从出生到退休
  5. 线程同步:别抢了,排队!
  6. 线程池:线程界的劳务派遣公司
  7. 常见坑点:别踩雷!
  8. 实战演练:手把手教学
  9. 高级技巧:从青铜到王者

🎯 开篇:多线程是个啥玩意儿?

🤔 官方定义 vs 人话翻译

官方定义:多线程是指在一个程序中同时运行多个线程,每个线程可以独立执行不同的任务。

人话翻译:就像你一边吃火锅🍲一边刷抖音📱一边聊微信💬,同时干三件事,效率杠杠的!

🎭 超形象比喻

想象你开了一家餐厅🍽️:

单线程餐厅(惨淡经营版):
老板一人干所有活 → 点菜、做菜、上菜、收钱
顾客A点菜 → 老板去做 → 做完上菜 → 收钱
顾客B点菜 → 等顾客A吃完才能点 😭
结果:顾客排长队,生意惨淡

多线程餐厅(火爆经营版):
👨‍🍳 厨师线程:专心做菜
🧑‍💼 服务员线程:点菜、上菜
💰 收银员线程:收钱找零
📦 洗碗工线程:洗碗打扫

顾客AB、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. 程序员的咖啡因多线程

单线程程序员:喝一杯咖啡,写一段代码,睡一觉
多线程程序员:喝咖啡同时写代码,写代码同时debugdebug同时修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 {
        反思();
        重来();
    }
}

永不放弃,直到成功! 💪🔥