Java 线程协作:工厂车间里的工人调度故事

39 阅读4分钟

一、车间里的工人:线程的日常工作

在 Java 的 "工厂车间" 里,每个线程都是一个忙碌的工人。他们需要协作完成任务,而waitnotifysleepinterrupt等方法就是他们之间的 "工作指令"。让我们通过车间场景理解这些指令的作用。

1. sleep:工人的强制休息

sleep就像工人累了,需要强制休息一段时间,休息时不释放手中的工具(锁):

java

// 工人A:每工作2秒休息1秒
class WorkerA implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("WorkerA: 正在加工零件 " + i);
            try {
                Thread.sleep(1000); // 休息1秒,不释放锁
            } catch (InterruptedException e) {
                System.out.println("WorkerA: 被打断休息");
                e.printStackTrace();
            }
        }
    }
}

// 启动工人A
new Thread(new WorkerA()).start();

特点

  • 属于Thread类的静态方法
  • 休息时不释放工具(锁)
  • 到时间自动醒来,或被interrupt打断

2. wait:等待材料的工人

wait就像工人在等待原材料,需要放下工具(释放锁),直到材料到达被通知:

java

// 原材料仓库
class MaterialWarehouse {
    private boolean hasMaterial = false;
    private final Object lock = new Object(); // 仓库门锁

    // 工人等待材料
    public void waitForMaterial() {
        synchronized (lock) { // 获得仓库门锁
            System.out.println("工人: 等待原材料...");
            try {
                lock.wait(); // 放下工具,等待通知
                System.out.println("工人: 收到原材料通知,继续工作");
            } catch (InterruptedException e) {
                System.out.println("工人: 等待被中断");
                e.printStackTrace();
            }
        }
    }

    // 通知工人材料已到
    public void notifyMaterialArrived() {
        synchronized (lock) {
            hasMaterial = true;
            lock.notify(); // 通知一个等待的工人
            // lock.notifyAll(); // 通知所有等待的工人
            System.out.println("仓库: 原材料已送达,通知工人");
        }
    }
}

// 使用示例
MaterialWarehouse warehouse = new MaterialWarehouse();
new Thread(() -> warehouse.waitForMaterial()).start();
// 5秒后通知材料到达
Thread.sleep(5000);
warehouse.notifyMaterialArrived();

特点

  • 属于Object类的实例方法
  • 必须在synchronized块中使用
  • 等待时释放工具(锁)
  • 可被notify/notifyAll唤醒,或超时醒来,或被interrupt打断

二、车间调度指令:线程控制方法

1. interrupt:车间里的紧急呼叫

interrupt就像车间管理员大喊一声,通知工人中断当前等待或休息:

java

class InterruptibleWorker implements Runnable {
    private Thread thread;

    @Override
    public void run() {
        thread = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            System.out.println("Worker: 工作中 " + i);
            try {
                // 模拟等待或休息
                if (i == 5) {
                    System.out.println("Worker: 开始等待材料");
                    synchronized (this) {
                        this.wait(); // 等待时可被interrupt打断
                    }
                } else {
                    Thread.sleep(1000); // 休息时可被interrupt打断
                }
            } catch (InterruptedException e) {
                System.out.println("Worker: 收到中断通知,结束工作");
                thread.interrupt(); // 重新设置中断标志
                return;
            }
        }
    }
}

// 启动并中断工人
InterruptibleWorker worker = new InterruptibleWorker();
Thread workerThread = new Thread(worker);
workerThread.start();
Thread.sleep(3000);
workerThread.interrupt(); // 3秒后中断工人

关键原理

  • 不直接终止线程,而是设置中断标志
  • 仅当线程在wait/sleep/join时才会抛出异常
  • 非阻塞状态下调用interrupt仅设置标志,需手动检查

2. yield:工人的短暂礼让

yield就像工人暂时停下,让其他工人先干,但可能很快又继续:

java

class YieldingWorker implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Worker: 工作进度 " + i);
            if (i % 2 == 0) {
                System.out.println("Worker: 礼让其他工人");
                Thread.yield(); // 礼让CPU
            }
        }
    }
}

// 启动两个工人,观察礼让效果
new Thread(new YieldingWorker()).start();
new Thread(new YieldingWorker()).start();

特点

  • Running状态转为Runnable,可能很快再次运行
  • 不释放锁,仅出让 CPU 时间
  • 无法保证礼让成功,取决于调度器

三、车间协作规则:关键区别对比

指令角色工具处理唤醒方式适用场景
sleep休息的工人不放下工具时间到 / 被呼叫 (interrupt)线程内定时休息
wait等材料的工人放下工具材料到 (notify)/ 全部到 (notifyAll)/ 时间到 / 被呼叫线程间协作等待
interrupt管理员呼叫-通知工人中断等待紧急情况中断线程
yield礼让的工人不放下工具自动重新获取 CPU平衡线程调度

核心代码对比:wait 与 sleep

java

// wait使用示例(需在synchronized块中)
synchronized (lock) {
    lock.wait(); // 放下工具,等待通知
}

// sleep使用示例
Thread.sleep(1000); // 休息1秒,不释放锁

四、车间管理最佳实践

  1. wait/notify 协作模式

    java

    // 标准等待-通知模式
    synchronized (monitor) {
        while (条件不满足) {
            monitor.wait(); // 等待条件满足
        }
        // 处理任务
    }
    
    // 通知方
    synchronized (monitor) {
        改变条件;
        monitor.notify(); // 通知等待者
    }
    
  2. 中断处理最佳实践

    java

    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                doWork();
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 重新设置中断标志
                break;
            }
        }
    }
    
  3. 避免死锁提示

    • 等待时使用wait(timeout)设置超时

    • 通知时优先使用notifyAll避免个别线程饥饿

    • 中断处理时及时释放资源

通过这个车间故事,我们理解了 Java 线程协作的核心机制。在实际开发中,合理使用这些 "调度指令" 可以让多线程协作更加高效,避免 "工人拥堵" 和 "资源浪费",打造流畅的多线程应用。