Java 线程控制方法大全:外卖骑手小明的多线程历险记

101 阅读5分钟

将通过外卖骑手小明的故事,为你生动讲解Thread中的sleep、wait、join、yield等方法的使用和原理。准备好进入多线程的奇妙世界吧!

角色设定

  • 🛵 小明:一个外卖骑手(代表一个线程)
  • 🏢 调度中心:线程调度器(CPU调度器)
  • 📱 订单系统:共享资源(对象锁)
  • 👨 顾客:需要服务的对象
  • 🧑‍🤝‍🧑 同事骑手:其他线程

第一幕:小憩片刻 - sleep(long millis)

场景:小明送完一单后,距离下一单还有15分钟,他决定在公园长椅上小睡一会。

java

public void run() {
    while (hasOrders) {
        deliverOrder();  // 送餐
        try {
            System.out.println("小明:送完一单,小睡15分钟");
            Thread.sleep(15 * 60 * 1000); // 睡眠15分钟
            System.out.println("小明:睡醒了,精力充沛!");
        } catch (InterruptedException e) {
            System.out.println("小明:被紧急订单吵醒了!");
        }
    }
}

原理剖析

  1. 不释放锁:小明睡觉时仍然拿着订单系统手机(不会释放任何锁)
  2. 精确计时:使用操作系统定时器(nanosleep系统调用)
  3. 中断响应:睡眠中收到中断信号会抛出InterruptedException
  4. 状态变化:线程状态从RUNNING变为TIMED_WAITING

源码实现

java

// Thread.java
public static void sleep(long millis) throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    
    if (millis == 0) {
        // 当时间为0时,触发线程让步
        Thread.yield();
        return;
    }
    
    long start = System.nanoTime();
    long duration = millis * 1_000_000;
    
    // 循环处理虚假唤醒和中断
    do {
        // 调用本地方法实现睡眠
        sleep0(millis);
    } while ((System.nanoTime() - start) < duration);
}

// 本地方法实现(JVM内部)
private static native void sleep0(long millis) throws InterruptedException;

第二幕:等待顾客 - wait()/notify()

场景:小明到达顾客楼下,顾客说"等我5分钟下来取"。小明必须等待顾客出现。

java

public void waitForCustomer() {
    synchronized (lock) { // 获取订单系统锁
        try {
            System.out.println("小明:到达楼下,等待顾客");
            lock.wait(5 * 60 * 1000); // 最多等5分钟
            
            if (customerArrived) {
                System.out.println("小明:顾客来了,交付外卖");
            } else {
                System.out.println("小明:5分钟到了,顾客没来");
            }
        } catch (InterruptedException e) {
            System.out.println("小明:被调度中心叫走了");
        }
    }
}

// 顾客出现时(另一个线程)
public void customerArrives() {
    synchronized (lock) {
        customerArrived = true;
        lock.notify(); // 通知小明
    }
}

原理剖析

  1. 释放锁:调用wait()时,小明会交出订单系统手机(释放锁)
  2. 等待队列:小明进入对象的等待队列
  3. 唤醒机制:notify()随机唤醒一个,notifyAll()唤醒所有
  4. 条件等待:必须配合synchronized使用

源码实现

java

// Object.java
public final void wait() throws InterruptedException {
    wait(0); // 调用带超时的wait
}

public final void wait(long timeout) throws InterruptedException {
    // ...
    synchronized (this) {
        // 将当前线程添加到等待集
        addToWaitSet(Thread.currentThread());
        // 释放锁
        releaseLock();
        // 挂起线程
        park(timeout);
        // 被唤醒后重新获取锁
        acquireLock();
        // 从等待集中移除
        removeFromWaitSet(Thread.currentThread());
    }
}

第三幕:团队协作 - join()

场景:小明和小红接到一个大订单,需要两人一起配送。小明对小红说:"我在路口等你,你到了我们一起走"。

java

Thread redThread = new Thread(() -> {
    System.out.println("小红:开始配送");
    try {
        Thread.sleep(2000); // 配送需要2分钟
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("小红:到达路口");
});

redThread.start();
System.out.println("小明:到达路口,等待小红");
try {
    redThread.join(); // 小明等待小红
    System.out.println("小明和小红:一起出发!");
} catch (InterruptedException e) {
    System.out.println("小明:不等了,先走了");
}

原理剖析

  1. 线程同步:join()本质是等待目标线程终止
  2. 实现机制:内部使用wait()实现
  3. 超时控制:join(millis)可以设置最大等待时间

源码实现

java

// Thread.java
public final void join() throws InterruptedException {
    join(0);
}

public final synchronized void join(long millis) 
    throws InterruptedException {
    long start = System.currentTimeMillis();
    long delay = millis;
    
    while (isAlive()) { // 当目标线程存活时
        if (delay <= 0) { // 超时则退出
            break;
        }
        wait(delay); // 当前线程等待
        long now = System.currentTimeMillis();
        delay = millis - (now - start); // 计算剩余时间
    }
}

第四幕:礼让三分 - yield()

场景:在早高峰的十字路口,小明主动减速让其他骑手先过。

java

public void run() {
    for (int i = 0; i < 10; i++) {
        if (i % 3 == 0) {
            System.out.println("小明:礼让其他骑手");
            Thread.yield(); // 主动让出CPU
        }
        System.out.println("小明:配送中..." + i);
    }
}

原理剖析

  1. 线程让步:提示调度器当前线程愿意让出CPU
  2. 非强制:调度器可以忽略此提示
  3. 适用场景:调试或优化长时间运行的循环
  4. 状态不变:线程仍保持RUNNABLE状态

源码实现

java

// Thread.java
public static native void yield();

// HotSpot JVM实现(c++)
void os::yield() {
    sched_yield(); // Linux系统调用
    // Windows: SwitchToThread()
    // macOS: sched_yield()
}

第五幕:光荣退役 - exit()

场景:小明完成一天工作,结束任务回家(线程正常终止)。

java

public void run() {
    try {
        while (!isWorkDone) {
            deliverOrder();
        }
    } finally {
        System.out.println("小明:下班回家!");
        // 线程自动调用exit()
    }
}

原理剖析

  1. 自动调用:线程正常结束时自动执行
  2. 资源清理:释放线程资源,通知等待线程
  3. 状态变更:线程状态变为TERMINATED

源码实现

java

// Thread.java(简化)
private void exit() {
    // 1. 清理ThreadLocal资源
    ThreadLocal.cleanupThread(this);
    
    // 2. 通知等待该线程终止的线程(join的线程)
    synchronized (this) {
        notifyAll();
    }
    
    // 3. 移除线程引用,帮助GC
    threadLocals = null;
    inheritableThreadLocals = null;
    
    // 4. 设置线程状态为TERMINATED
    setState(ThreadState.TERMINATED);
}

方法对比总结表

方法所属类作用释放锁中断响应状态变化
sleep()Thread定时等待TIMED_WAITING
wait()Object条件等待WAITING
join()Thread等待线程结束WAITING
yield()Thread让出CPURUNNABLE
exit()Thread线程退出(内部)-TERMINATED

最佳实践指南

  1. sleep vs wait

    java

    // 需要定时暂停用sleep
    Thread.sleep(1000);
    
    // 需要条件等待用wait
    synchronized(lock) {
        while (!condition) {
            lock.wait();
        }
    }
    
  2. join的正确用法

    java

    Thread worker = new Thread(task);
    worker.start();
    
    // 设置合理的超时时间
    worker.join(5000); // 最多等待5秒
    
    if (worker.isAlive()) {
        worker.interrupt(); // 超时后中断
    }
    
  3. yield的注意事项

    java

    // 不要过度使用yield
    for (int i = 0; i < 1000000; i++) {
        if (i % 1000 == 0) {
            Thread.yield(); // 每1000次迭代让步一次
        }
        // 工作代码
    }
    
  4. 线程终止的正确方式

    java

    public class SafeStoppableThread extends Thread {
        private volatile boolean running = true;
        
        public void run() {
            while (running && !Thread.interrupted()) {
                // 执行任务
            }
            // 清理资源
        }
        
        public void stopGracefully() {
            running = false;
            interrupt(); // 双重保险
        }
    }
    

通过小明的外卖故事,我们深入理解了Java线程控制的核心方法及其实现原理。记住:

  1. sleep() 是定时小憩,不释放锁
  2. wait() 是条件等待,必须释放锁
  3. join() 是团队协作,等待队友
  4. yield() 是礼貌让步,不保证效果
  5. exit() 是光荣退役,自动调用

掌握这些方法的使用场景和底层原理,你就能编写出高效、稳定的多线程程序!