古法核心: Java线程核心入门

0 阅读14分钟

在广州图书馆充电中,拿起书架上的书籍就津津有味的啃了起来。

gzlib.jpg

线程的两种创建方式

方式一:实现 Runnable 接口

public class DoSomething implements Runnable {

    public void run() {
        System.out.println(Thread.currentThread().getName() + " I'm doing something!");
    }
}

调用方式

new Thread(new DoSomething()).start();

方式二:继承 Thread 类

public class DoSomethingToo extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " I'm doing something too!");
    }
}

调用方式

new DoSomethingToo().start();

两种方式对比

方式优点缺点
implements Runnable更灵活,可继承其他类需要包装成 Thread
extends Thread使用 getName() 更方便无法继承其他类

启动线程

  • start(): 启动线程(异步执行 run() 方法)
  • run(): 直接调用则同步执行,不会创建新线程

Thread 常用方法

  • Thread.currentThread().getName() - 获取当前线程名称
  • getName() - 获取线程名称(继承 Thread 时可用)

示例代码


线程状态

Monitor Lock(监视器锁):

  1. 等待队列
  2. 阻塞队列

为什么需要两个队列?

避免"惊群"和无效唤醒:

  1. 等待队列中的线程即便获得锁,条件可能还不满足,所以需要条件判断循环;
  2. 阻塞队列只关心锁本身,不关心业务条件

thread_state.png


接口与抽象类的结合

定义接口

public interface NumberPrinter {
    void printNumber(int number);
}

抽象类实现接口 + Runnable

public abstract class AbstractNumberPrinter implements NumberPrinter, Runnable {

    private int limit;

    public AbstractNumberPrinter(int limit) {
        this.limit = limit;
    }

    @Override
    public void run() {
        synchronized (AbstractNumberPrinter.class) {
            for (int i = 0; i < limit; i++) {
                if (acceptNumber(i)) {
                    printNumber(i);
                }
            }
        }
    }

    protected abstract boolean acceptNumber(int number);

    @Override
    public void printNumber(int number) {
        System.out.println(Thread.currentThread().getName() + " " + number);
    }
}

具体实现类

public class EvenNumberPrinter extends AbstractNumberPrinter {

    public EvenNumberPrinter(int limit) {
        super(limit);
    }

    @Override
    protected boolean acceptNumber(int number) {
        return number % 2 == 0;
    }
}
public class OddNumberPrinter extends AbstractNumberPrinter {

    public OddNumberPrinter(int limit) {
        super(limit);
    }

    @Override
    protected boolean acceptNumber(int number) {
        return number % 2 != 0;
    }
}

启动线程

int limit = 100;
new Thread(new EvenNumberPrinter(limit), "偶数线程").start();
new Thread(new OddNumberPrinter(limit), "奇数线程").start();

synchronized 同步

  • synchronized 保证同一时刻只有一个线程执行同步代码块
  • synchronized (xxx.class) - 锁定类的所有实例

模板方法模式

  • AbstractNumberPrinter 定义 run() 模板骨架
  • 子类只需实现 acceptNumber() 决定哪些数字需要打印
  • 父类控制流程,子类负责细节实现

示例代码


Thread.join() 等待线程结束

场景描述

暗夜精灵需要等小矮人起床后才能出发进入战斗。

原始输出(无 join)

暗夜精灵: "起——来——!!!!"
暗夜精灵: "出发吧!进入战斗!"
【小矮人 (有点晚)】: "哈——欠,唉,早上好,我来了,我来了。"

期望输出(有 join)

暗夜精灵: "起——来——!!!!"
【小矮人 (有点晚)】: "哈——欠,唉,早上好,我来了,我来了。"
暗夜精灵: "出发吧!进入战斗!"

实现方式

public class Main {
    public static void main(String[] args) {
        final Dwarf dwarf = new Dwarf("小矮人");
        final NightElf nightElf = new NightElf("暗夜精灵");

        final Thread dwarfThread = new Thread(() -> {
            dwarf.chargeIntoBattle(nightElf);
        });

        final Thread elfThread = new Thread(() -> {
            try {
                dwarfThread.join();  // 等待 dwarfThread 执行完毕
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            nightElf.chargeIntoBattle(dwarf);
        });

        dwarfThread.start();
        elfThread.start();
    }
}

Hero 抽象类

public abstract class Hero {
    private String name;

    public Hero(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public abstract void chargeIntoBattle(Hero hero);
}

Dwarf 实现(小矮人赖床)

public class Dwarf extends Hero {
    public Dwarf(String name) {
        super(name);
    }

    @Override
    public void chargeIntoBattle(Hero hero) {
        System.out.printf("%s: \"起——来——!!!!\"%n", hero.getName());
        try {
            Thread.sleep(5000);  // 模拟赖床 5 秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("【%s (有点晚)】: \"哈——欠,唉,早上好,我来了,我来了。\"%n", this.getName());
    }
}

NightElf 实现(暗夜精灵)

public class NightElf extends Hero {
    public NightElf(String name) {
        super(name);
    }

    @Override
    public void chargeIntoBattle(Hero hero) {
        System.out.printf("%s: \"出发吧!进入战斗!\"%n", this.getName());
    }
}

join() 核心要点

  • thread.join() - 等待指定线程执行完毕
  • thread.join(millis) - 等待指定线程,最多等待指定毫秒
  • 需要捕获 InterruptedException
  • 常用于线程间依赖关系:B 线程需要等 A 线程完成后再执行

示例代码


死锁(Deadlock)

场景描述

矮人和暗夜精灵约定在对方那里见面,然后一起出发。但由于他们都"先占先得"对方的家,导致谁也等不到谁。

实际输出(死锁)

矮人: "我们在暗夜精灵那儿见面,那我就先出发了。"
暗夜精灵: "我们在矮人那儿见面,那我就先出发了。"
(程序卡住,不再输出)


代码实现

Hero 类(关键)

public class Hero {
    private String name;

    public synchronized void visit(Hero hero) {
        try {
            Thread.sleep(50);  // 让另一个线程有机会获取锁
        } catch (InterruptedException e) { }
        
        System.out.printf("%s: \"我们在%s那儿见面,那我就先出发了。\"%n", 
            this.getName(), hero.getName());
        hero.receiveVisit(this);  // 尝试调用对方的 synchronized 方法
    }

    public synchronized void receiveVisit(Hero hero) {
        System.out.printf("%s: \"我们在%s家见面了。\"%n", this.getName(), this.getName());
    }
}

Main 启动两个线程

new Thread(() -> dwarf.visit(nightElf)).start();       // 线程1
new Thread(() -> nightElf.visit(dwarf)).start();       // 线程2

死锁原因分析

visualvm查看死锁

dead_lock.png

Found one Java-level deadlock:
=============================
"矮人线程":
  waiting to lock monitor 0x00000185c1fc9a70 (object 0x00000005aa97f810, a cn.comicjava.ch02.wow.deadlock.NightElf),
  which is held by "暗夜精灵线程"

"暗夜精灵线程":
  waiting to lock monitor 0x00000185c1fc9b50 (object 0x00000005aa97f7d0, a cn.comicjava.ch02.wow.deadlock.Dwarf),
  which is held by "矮人线程"

图解死锁过程

┌─────────────────────────────────────────────────────────────────────────┐
│                           死锁形成过程                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  线程1: dwarf.visit(nightElf)                                          │
│    ├─ 获取 dwarf 对象的锁 ✓                                             │
│    ├─ sleep(50) 让出 CPU                                                │
│    └─ 尝试调用 nightElf.receiveVisit() → 需要获取 nightElf 对象的锁 ✗    │
│                                                                         │
│  线程2: nightElf.visit(dwarf)                                           │
│    ├─ 获取 nightElf 对象的锁 ✓                                          │
│    ├─ sleep(50) 让出 CPU                                                │
│    └─ 尝试调用 dwarf.receiveVisit() → 需要获取 dwarf 对象的锁 ✗         │
│                                                                         │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  持有资源的线程想要获取对方持有的资源,形成循环等待                       │
│                                                                         │
│     ┌──────────────┐         持有 dwarf         ┌──────────────┐        │
│     │   线程1      │ ─────────────────────────▶│    dwarf     │        │
│     │  (矮人线程)   │                            │   (矮人)     │        │
│     └──────────────┘                            └──────┬───────┘        │
│            │                                            │               │
│            │ 持有 nightElf                               │ 持有 dwarf    │
│            │                                            │               │
│            │ 等待 dwarf                                 │ 等待 nightElf │
│            │                                            │               │
│     ┌──────┴───────┐                            ┌──────┴───────┐        │
│     │  nightElf    │◀─────────────────────────│   线程2      │        │
│     │   (暗夜精灵)  │         持有 nightElf    │ (暗夜精灵线程) │        │
│     └──────────────┘                            └──────────────┘        │
│                                                                         │
│                    💀 死锁!双方都在等待对方释放锁                        │
└─────────────────────────────────────────────────────────────────────────┘

死锁四个必要条件(Coffman 条件)

条件描述本例中是否满足
互斥条件一个资源一次只能被一个线程持有synchronized 保证
占有并等待线程持有资源的同时请求其他资源✓ 持有自己的锁,等对方的锁
不可抢占资源不能被强制释放✓ synchronized 无法抢占
循环等待存在循环链:T1 等待 T2,T2 等待 T1✓ 线程1等线程2,线程2等线程1

解决方案:使用共享锁对象

核心思想

打破"占有并等待"条件:让两个线程使用同一个锁对象,这样就能保证串行执行。

正确输出

矮人: "我们在暗夜精灵那儿见面,那我就先出发了。"
暗夜精灵: "我们在我家见面了。"
暗夜精灵: "我们在矮人那儿见面,那我就先出发了。"
矮人: "我们在我家见面了。"

Hero 类修改

public class Hero {
    private String name;
    private Object lock;  // 新增:可设置的锁对象

    public Hero(String name) {
        this.name = name;
    }

    public Object getLock() {
        return lock;
    }

    public void setLock(Object lock) {
        this.lock = lock;
    }

    public void visit(Hero hero) {
        synchronized (this.getLock()) {  // 使用共享的 lock
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) { }

            System.out.printf("%s: \"我们在%s那儿见面,那我就先出发了。\"%n",
                this.getName(), hero.getName());
            hero.receiveVisit(this);
        }
    }

    public void receiveVisit(Hero hero) {
        synchronized (this.getLock()) {  // 使用共享的 lock
            System.out.printf("%s: \"我们在我家见面了。\"%n", this.getName());
        }
    }
}

Main 类修改(关键)

public class Main {
    public static void main(String[] args) {
        final Dwarf dwarf = new Dwarf("矮人");
        final NightElf nightElf = new NightElf("暗夜精灵");

        // 创建共享锁对象
        Object lock = new Object();
        dwarf.setLock(lock);      // 两个英雄使用同一个锁
        nightElf.setLock(lock);

        new Thread(() -> dwarf.visit(nightElf), "矮人线程").start();
        new Thread(() -> nightElf.visit(dwarf), "暗夜精灵线程").start();
    }
}

图解解决方案

┌─────────────────────────────────────────────────────────────────────────┐
│                     解决方案:使用共享锁对象                             │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  Main 中创建共享锁: Object lock = new Object();                        │
│                                                                         │
│     ┌─────────────────────────────────────────────────────────────┐     │
│     │                    共享的 lock 对象                          │     │
│     │                        ┌───────┐                            │     │
│     │                        │  lock │                            │     │
│     │                        └───┬───┘                            │     │
│     │                   ┌────────┴────────┐                       │     │
│     │                   ▼                 ▼                       │     │
│     │             ┌──────────┐       ┌──────────┐                 │     │
│     │             │  dwarf   │       │ nightElf │                 │     │
│     │             │  (矮人)   │       │(暗夜精灵) │                 │     │
│     │             └──────────┘       └──────────┘                 │     │
│     └─────────────────────────────────────────────────────────────┘     │
│                                                                         │
│  线程1: dwarf.visit(nightElf)                                          │
│    └─ 获取 lock 锁 ✓ → 完成后再让出 CPU                                 │
│                                                                         │
│  线程2: nightElf.visit(dwarf)                                          │
│    └─ 等线程1释放 lock 后 → 获取 lock 锁 ✓ → 完成                      │
│                                                                         │
│                    ✅ 串行执行,不会死锁!                                │
└─────────────────────────────────────────────────────────────────────────┘

为什么不会死锁?

原因说明
同一时刻只有一个线程持有锁synchronized (sharedLock) 保证
打破循环等待不再是线程1持A等B,线程2持B等A
有序执行线程1执行完 → 释放锁 → 线程2获取锁执行

示例代码


解决方案二:使用 ReentrantLock + tryLock

核心思想

使用 Lock.tryLock() 尝试获取锁,而不是阻塞等待。如果获取失败,立即放弃,避免无限等待导致的死锁。

正确输出

(任务1)矮人: "我们在暗夜精灵那儿见面,那我就先出发了。"
(任务1)暗夜精灵: "我们在我家见面了。"
(任务2)暗夜精灵: "抱歉,我在忙,下次再约吧。"

Hero 类实现

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Hero {
    private String name;
    private Lock lock = new ReentrantLock();

    public Hero(String name) {
        this.name = name;
    }

    public Lock getLock() {
        return lock;
    }

    // 尝试同时获取两个锁
    private boolean meetAt(Hero hero) {
        boolean iLocked = false;
        boolean heroLocked = false;
        try {
            iLocked = lock.tryLock();           // 非阻塞获取锁
            heroLocked = hero.getLock().tryLock(); // 非阻塞获取锁
        } finally {
            // 如果没有同时获取到两个锁,释放已获取的
            if (!(iLocked && heroLocked)) {
                if (iLocked) lock.unlock();
                if (heroLocked) hero.getLock().unlock();
            }
        }
        // 返回 true 时锁仍在持有状态,需要调用者释放
        return iLocked && heroLocked;
    }

    // 释放两个锁
    public void unlockBoth(Hero hero) {
        try {
            hero.getLock().unlock(); // 先释放 hero 的锁
        } finally {
            lock.unlock();          // 最后释放自己的锁
        }
    }

    public void visit(Hero hero) {
        if (meetAt(hero)) {
            try {
                System.out.printf("(%s)%s: \"我们在%s那儿见面,那我就先出发了。\"%n",
                    Thread.currentThread().getName(), this.getName(), hero.getName());
                hero.receiveVisit(this);
            } finally {
                unlockBoth(hero);  // 正确释放两个锁
            }
        } else {
            System.out.printf("(%s)%s: \"抱歉,我在忙,下次再约吧。\"%n",
                Thread.currentThread().getName(), this.getName());
        }
    }

    public void receiveVisit(Hero hero) {
        System.out.printf("(%s)%s: \"我们在我家见面了。\"%n",
            Thread.currentThread().getName(), this.getName());
    }
}

图解 tryLock 流程

┌─────────────────────────────────────────────────────────────────────────┐
│                     tryLock() 非阻塞获取锁                              │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  线程1: dwarf.visit(nightElf)                                          │
│    ├─ lock.tryLock() → ✓ 成功                                           │
│    ├─ hero.getLock().tryLock() → ✓ 成功                                 │
│    └─ 执行 visit 逻辑 → 完成                                             │
│                                                                         │
│  线程2: nightElf.visit(dwarf)                                          │
│    ├─ lock.tryLock() → ✗ 失败(被线程1持有)                            │
│    ├─ 立即放弃,释放已获取的锁                                           │
│    └─ 输出 "抱歉,我在忙,下次再约吧。"                                  │
│                                                                         │
│                    ✅ 不会出现死锁!                                      │
└─────────────────────────────────────────────────────────────────────────┘

可能出现的输出情况

场景输出说明
A任务1: 我们在暗夜精灵那儿见面...
任务1: 我们在我家见面了。
任务2: 抱歉,我在忙...
任务1先获取两个锁
B任务2: 我们在矮人那儿见面...
任务2: 我们在我家见面了。
任务1: 抱歉,我在忙...
任务2先获取两个锁
C任务1: 抱歉,我在忙...
任务2: 抱歉,我在忙...
都没有获取到两个锁

与 synchronized 对比

特性synchronizedReentrantLock + tryLock
获取锁阻塞等待非阻塞尝试
死锁风险高(等待无限期)低(获取失败立即放弃)
灵活性高(可设置超时、可中断)
代码复杂度较高(需手动释放锁)

示例代码


活锁(Livelock)

场景描述

矮人和暗夜精灵都口渴想喝啤酒,但只有一瓶啤酒。他们互相礼让,结果谁也喝不到,程序无限循环。

输出(无限重复):

矮人: "你先喝吧,我的朋友!"
暗夜精灵: "你先喝吧,我的朋友!"
矮人: "你先喝吧,我的朋友!"
暗夜精灵: "你先喝吧,我的朋友!"
...
(程序永远不会停止)

代码实现

Beer 类(共享资源)

public class Beer {
    private Hero owner;

    public Beer(Hero hero) {
        this.owner = hero;
    }

    public synchronized Hero getOwner() {
        return owner;
    }

    public synchronized void setOwner(Hero hero) {
        this.owner = hero;
    }

    public synchronized void drink() {
        System.out.printf("%s 喝了啤酒%n", owner.getName());
    }
}

Hero 类(核心逻辑)

public class Hero {
    private String name;
    private boolean isThirsty = true;

    public void drink(Beer beer, Hero drinkingPartner) {
        while (this.isThirsty()) {  // 只要还口渴就一直循环
            if (beer.getOwner() != this) {
                continue;  // 啤酒不是我的,等待
            } else if (drinkingPartner.isThirsty()) {
                // 对方也口渴,我让给他
                System.out.printf("%s: \"你先喝吧,我的朋友!\"%n", name);
                beer.setOwner(drinkingPartner);
            } else {
                // 对方喝完了,我喝
                beer.drink();
                this.isThirsty = false;
                System.out.printf("%s: \"真好喝!\"%n", name);
                beer.setOwner(drinkingPartner);
            }
        }
    }
}

Main 启动两个线程

public class Main {
    public static void main(String[] args) {
        final Dwarf dwarf = new Dwarf("矮人");
        final NightElf nightElf = new NightElf("暗夜精灵");
        final Beer beer = new Beer(dwarf);

        new Thread(() -> dwarf.drink(beer, nightElf)).start();
        new Thread(() -> nightElf.drink(beer, dwarf)).start();
    }
}

死锁 vs 活锁

特性死锁(Deadlock)活锁(Livelock)
线程状态阻塞等待,不做任何操作持续运行,但无法前进
CPU 消耗正常较高(忙等待)
程序表现完全卡住无限循环执行
原因循环等待过度礼让/重复重试

图解活锁过程

┌─────────────────────────────────────────────────────────────────────────┐
│                           活锁形成过程                                   │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  初始状态: 啤酒属于矮人                                                 │
│                                                                         │
│     ┌──────────────────────────────────────────────────────────────┐    │
│     │                         啤酒                                 │    │
│     │                         🥛                                   │    │
│     │                      属于矮人                                 │    │
│     └──────────────────────────────────────────────────────────────┘    │
│                                                                         │
│  线程1: 矮人.drink(beer, 暗夜精灵)                                      │
│    ├─ beer.getOwner() == 矮人 → 拥有啤酒                              │
│    ├─ 暗夜精灵.isThirsty() == true → 对方也口渴                        │
│    ├─ 输出 "你先喝吧,我的朋友!"                                       │
│    └─ beer.setOwner(暗夜精灵) → 啤酒让给对方                           │
│                                                                         │
│  线程2: 暗夜精灵.drink(beer, 矮人)                                      │
│    ├─ beer.getOwner() == 暗夜精灵 → 拥有啤酒                           │
│    ├─ 矮人.isThirsty() == true → 对方也口渴                            │
│    ├─ 输出 "你先喝吧,我的朋友!"                                       │
│    └─ beer.setOwner(矮人) → 啤酒让回给对方                             │
│                                                                         │
│                    💫 循环往复,永远无法喝到啤酒!                        │
└─────────────────────────────────────────────────────────────────────────┘

活锁特点

  • 线程没有阻塞:一直在执行 while 循环
  • 无法完成任务:虽然在做动作(让啤酒),但永远达不到目标
  • 资源未被占用:CPU 在忙,但啤酒被反复转手
  • 与死锁的区别:死锁是完全不动,活锁是都在动但做无用功

活锁的另一种理解

想象两个礼貌的人在狭窄的走廊相遇:

  • 死锁:两人都站在原地不动,互相让对方先过
  • 活锁:两人都主动让路,但方向一致,左躲右闪还是撞不上

示例代码


活锁解决方案:礼让次数限制

问题分析

原代码中 drinkingPartner.isThirsty() 始终为 true,永远无法进入 else 分支,isThirsty 永远无法设为 false

解决思路

每个英雄最多礼让 N 次,超过后强制执行,不再让步。

正确输出

矮人: "你先喝吧,我的朋友!"
矮人: "你先喝吧,我的朋友!"
矮人: "你先喝吧,我的朋友!"
暗夜精灵: "你先喝吧,我的朋友!"
矮人: "你先喝吧,我的朋友!"
暗夜精灵: "你先喝吧,我的朋友!"
矮人: "你先喝吧,我的朋友!"
暗夜精灵: "你先喝吧,我的朋友!"
矮人: "不客气了,我先喝!"
矮人 喝了啤酒
矮人: "真好喝!"
暗夜精灵 喝了啤酒
暗夜精灵: "真好喝!"

核心代码修改

public void drink(Beer beer, Hero drinkingPartner) {
    int courtesyCount = 0;  // 礼让次数计数
    while (this.isThirsty()) {
        if (beer.getOwner() != this) {
            continue;
        } else if (drinkingPartner.isThirsty()) {
            if (courtesyCount < 3) {
                // 礼让不超过 3 次
                System.out.printf("%s: \"你先喝吧,我的朋友!\"%n", name);
                beer.setOwner(drinkingPartner);
                courtesyCount++;
            } else {
                // 礼让 3 次后强制喝
                System.out.printf("%s: \"不客气了,我先喝!\"%n", name);
                beer.drink();
                this.isThirsty = false;
                beer.setOwner(drinkingPartner);
            }
        } else {
            beer.drink();
            this.isThirsty = false;
            System.out.printf("%s: \"真好喝!\"%n", name);
            beer.setOwner(drinkingPartner);
        }
    }
}

等待与通知(wait/notify)

场景描述

图书馆 9:00 才开门营业,学生和上班族早早来到门口等待。开门后,管理员一声"开馆了!",所有人同时涌入抢座。

gzlib.jpg

实际输出

未到9:00,广州图书馆还没开门,小明等待中
未到9:00,广州图书馆还没开门,菲利普等待中
广州图书馆开馆了!!!
菲利普: "抢到座位了,打开电脑看看招聘/改改简历"
小明: "抢到座位,开启备考心的一天"

核心概念

wait/notify 机制

方法作用说明
wait()线程进入等待状态自动释放对象的锁
notify()唤醒一个等待线程随机唤醒一个
notifyAll()唤醒所有等待线程全部唤醒,由 JVM 决定调度

重要前提

  • wait()notify()notifyAll() 必须synchronized 代码块内调用
  • 调用这些方法的对象就是锁对象

代码实现

Library 类(图书馆)

public class Library {
    private String name;
    private volatile boolean open = false;

    public Library(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public boolean isOpen() {
        return this.open;
    }

    public void setOpen(boolean open) {
        this.open = open;
        if (open) {
            System.out.printf("%s开馆了!!!%n", this.getName());
        }
    }
}

Person 抽象类(等待者)

public abstract class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void rushToOccupySeat(Library library) {
        synchronized (library) {
            while (!library.isOpen()) {
                System.out.printf("未到9:00,%s还没开门,%s等待中%n",
                    library.getName(), this.getName());
                try {
                    library.wait();
                } catch (InterruptedException e) {
                }
            }
        }
        this.success();
    }

    protected abstract void success();
}

Main 启动线程 + 开门通知

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Student stu = new Student("小明");
        Worker worker = new Worker("菲利普");
        Library gzLib = new Library("广州图书馆");

        Thread stuThread = new Thread(() -> stu.rushToOccupySeat(gzLib));
        Thread workThread = new Thread(() -> worker.rushToOccupySeat(gzLib));

        stuThread.start();
        workThread.start();

        Thread.sleep(1000);

        synchronized (gzLib) {
            gzLib.setOpen(true);
            gzLib.notifyAll();
        }
    }
}

图解等待与通知过程

┌────────────────────────────────────────────────────────────────────┐
│                    等待与通知(wait/notify)流程                     │
├────────────────────────────────────────────────────────────────────┤
│                                                                    │
│  第一阶段:线程等待                                                  │
│                                                                    │
│     ┌──────────────────────────────────────────┐                  │
│     │           gzLib 对象                       │                  │
│     │  ┌────────────┐  ┌─────────────────┐     │                  │
│     │  │ 等待队列    │  │  阻塞队列        │     │                  │
│     │  │ Student    │  │                 │     │                  │
│     │  │ Worker     │  │                 │     │                  │
│     │  └────────────┘  └─────────────────┘     │                  │
│     │        ▲                                   │                  │
│     │        │ library.wait()                    │                  │
│     │        │ 释放锁,进入等待队列               │                  │
│     └────────┼──────────────────────────────────┘                  │
│              │                                               │
│              ▼                                               │
│     Main 线程持有锁,模拟等待 1 秒                              │
│                                                                    │
│  第二阶段:通知唤醒                                                  │
│                                                                    │
│     gzLib.setOpen(true) → 设置开门标志                            │
│     gzLib.notifyAll()    → 唤醒等待队列中的所有线程                 │
│                                                                    │
│     Student Thread ──▶ 获取锁 ──▶ 执行 success()                 │
│     Worker Thread  ──▶ 获取锁 ──▶ 执行 success()                 │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

为什么用 while 而不是 if?

// ❌ 错误写法
if (!library.isOpen()) {
    library.wait();
}

// ✅ 正确写法
while (!library.isOpen()) {
    library.wait();
}

原因:防止假唤醒(Spurious Wakeup) - wait() 可能无原因被唤醒,使用 while 确保醒来后重新检查条件。


为什么用 notifyAll 而不是 notify?

  • 有两个线程在等待
  • notify() 随机唤醒一个,另一个会永远等待
  • notifyAll() 唤醒所有,确保全部被通知

与 Thread.join() 的区别

特性wait/notifyThread.join()
等待条件自定义条件线程执行完毕
唤醒方式主动调用 notify自动唤醒
释放锁wait() 会释放锁不会释放锁

示例代码


(待续...)