将通过外卖骑手小明的故事,为你生动讲解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("小明:被紧急订单吵醒了!");
}
}
}
原理剖析:
- 不释放锁:小明睡觉时仍然拿着订单系统手机(不会释放任何锁)
- 精确计时:使用操作系统定时器(
nanosleep系统调用) - 中断响应:睡眠中收到中断信号会抛出InterruptedException
- 状态变化:线程状态从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(); // 通知小明
}
}
原理剖析:
- 释放锁:调用wait()时,小明会交出订单系统手机(释放锁)
- 等待队列:小明进入对象的等待队列
- 唤醒机制:notify()随机唤醒一个,notifyAll()唤醒所有
- 条件等待:必须配合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("小明:不等了,先走了");
}
原理剖析:
- 线程同步:join()本质是等待目标线程终止
- 实现机制:内部使用wait()实现
- 超时控制: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);
}
}
原理剖析:
- 线程让步:提示调度器当前线程愿意让出CPU
- 非强制:调度器可以忽略此提示
- 适用场景:调试或优化长时间运行的循环
- 状态不变:线程仍保持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()
}
}
原理剖析:
- 自动调用:线程正常结束时自动执行
- 资源清理:释放线程资源,通知等待线程
- 状态变更:线程状态变为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 | 让出CPU | ❌ | ❌ | RUNNABLE |
| exit() | Thread | 线程退出(内部) | ✅ | - | TERMINATED |
最佳实践指南
-
sleep vs wait:
java
// 需要定时暂停用sleep Thread.sleep(1000); // 需要条件等待用wait synchronized(lock) { while (!condition) { lock.wait(); } } -
join的正确用法:
java
Thread worker = new Thread(task); worker.start(); // 设置合理的超时时间 worker.join(5000); // 最多等待5秒 if (worker.isAlive()) { worker.interrupt(); // 超时后中断 } -
yield的注意事项:
java
// 不要过度使用yield for (int i = 0; i < 1000000; i++) { if (i % 1000 == 0) { Thread.yield(); // 每1000次迭代让步一次 } // 工作代码 } -
线程终止的正确方式:
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线程控制的核心方法及其实现原理。记住:
sleep()是定时小憩,不释放锁wait()是条件等待,必须释放锁join()是团队协作,等待队友yield()是礼貌让步,不保证效果exit()是光荣退役,自动调用
掌握这些方法的使用场景和底层原理,你就能编写出高效、稳定的多线程程序!