Thread.sleep(long millis)使主线程休眠指定毫秒数,或调用其他线程的`
核心概念与典型场景
主线程等待的本质是线程间同步,即通过特定机制控制线程执行顺序,常见需求包括:
- 等待子线程完成任务(如后台计算完成后返回结果)
- 定时延迟(模拟倒计时或节拍器)
- 条件触发(仅当某变量达到阈值时继续执行)
- 协调多线程生命周期(确保所有工作线程就绪后再启动主流程)
主流实现方案详解
方案1:Thread.sleep()——简单粗暴的时间控制
| 特性 | 描述 |
|---|---|
| 作用对象 | 当前正在运行的线程(含主线程) |
| 参数类型 | long millis(毫秒级精度) |
| 是否可中断 | ️ 抛出InterruptedException响应中断 |
| 精确性 | 受系统调度影响,实际睡眠时间≥指定值 |
| 典型用途 | 短暂让步CPU资源、测试性能瓶颈 |
代码示例:
public class SleepDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程开始等待...");
Thread.sleep(2000); // 主线程休眠2秒
System.out.println("主线程恢复执行");
}
}
️ 注意:此方法仅能实现无条件的时间等待,无法感知外部状态变化,若需动态响应事件,需结合其他机制。
方案2:Thread.join()——强制等待目标线程终止
| 特性 | 描述 |
|---|---|
| 作用对象 | 指定线程(非自身) |
| 参数类型 | void join() / long join(long miils) |
| 是否可中断 | ️ 捕获InterruptedException |
| 阻塞特性 | 永久阻塞直至目标线程死亡 |
| 典型用途 | 确保子线程完全结束后再继续主线程逻辑 |
代码示例:
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
try {
// 模拟耗时操作
Thread.sleep(3000);
System.out.println("子线程工作完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
worker.start(); // 启动子线程
System.out.println("主线程等待子线程...");
worker.join(); // 主线程在此阻塞,直到子线程结束
System.out.println("主线程继续执行");
}
}
进阶技巧:若需设置最长等待时间,可改用worker.join(5000),超时后自动解除阻塞。
方案3:CountDownLatch——精确控制并发计数器
| 特性 | 描述 |
|---|---|
| 核心功能 | 初始化计数器N,每调用一次countDown()减1,await()阻塞至计数归零 |
| 线程安全性 | ️ 内部基于CAS无锁算法 |
| 重复使用性 | 一次性使用(需重新new实例) |
| 典型用途 | 协调多个线程的启动时机、分阶段执行任务 |
代码示例:
import java.util.concurrent.CountDownLatch;
public class LatchDemo {
public static void main(String[] args) throws InterruptedException {
final int workerCount = 3;
CountDownLatch latch = new CountDownLatch(workerCount); // 初始化计数器为3
// 创建3个子线程
for (int i = 0; i < workerCount; i++) {
new Thread(() -> {
try {
// 模拟工作负载
Thread.sleep((long)(Math.random() 2000));
System.out.println(Thread.currentThread().getName() + "完成任务");
} finally {
latch.countDown(); // 任务完成,计数器减1
}
}).start();
}
System.out.println("主线程等待所有子线程...");
latch.await(); // 阻塞主线程,直到计数器归零
System.out.println("所有子线程已完成,主线程继续执行");
}
}
优势对比表:
| 方法 | 适用场景 | 优点 | 缺点 |
|——————–|——————————|————————–|————————–|
| Thread.sleep() | 简单延时 | 实现简单 | 无法响应外部事件 |
| Thread.join() | 等待单个线程结束 | 语义明确 | 仅支持单对多关系 |
| CountDownLatch | 多线程同步/分阶段执行 | 灵活控制并发数量 | 不可重用,需预知线程数 |
| CyclicBarrier | 循环屏障(如回合制游戏) | 可重置计数器 | 复杂度较高 |
| Semaphore | 信号量控制资源访问 | 支持许可数动态调整 | 需手动管理信号量状态 |
方案4:Object.wait()/notify()——经典管程模式
此方案基于监视器模式,需配合synchronized关键字使用,适用于复杂的条件等待场景。
关键步骤:
- 获取对象的监视器锁(
synchronized(obj)) - 调用
obj.wait()进入等待集 - 其他线程修改共享变量后,调用
obj.notify()/notifyAll()唤醒等待线程 - 被唤醒的线程重新竞争锁并继续执行
代码示例:
public class WaitNotifyDemo {
private static boolean ready = false;
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread producer = new Thread(() -> {
synchronized (lock) {
System.out.println("生产者准备数据...");
ready = true; // 修改共享状态
lock.notifyAll(); // 唤醒消费者
}
});
Thread consumer = new Thread(() -> {
synchronized (lock) {
while (!ready) { // 判断条件
try {
lock.wait(); // 条件不满足时等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者开始消费数据");
}
});
consumer.start();
Thread.sleep(100); // 确保消费者先启动
producer.start();
}
}
️ 致命风险:若忘记在while循环中检查条件,可能导致虚假唤醒(Spurious Wakeup),必须始终将wait()放在while循环内!
高级场景优化策略
超时控制与异常处理
多数等待方法支持超时版本,防止无限期阻塞:
// CountDownLatch带超时的await
boolean success = latch.await(5, TimeUnit.SECONDS); // 最多等待5秒
if (!success) {
System.out.println("等待超时!");
}
响应中断请求
所有阻塞方法均应处理InterruptedException,及时退出等待状态:
try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 恢复中断状态
System.out.println("线程被中断!");
}
避免死锁的技巧
- 禁止在持有锁的情况下调用外部不确定行为的代码(如网络IO)
- 推荐使用
java.util.concurrent包下的工具类(如ReentrantLock+Condition替代原生wait/notify) - 遵循FIFO原则获取锁,避免交叉调用导致的环状依赖
相关问答FAQs
Q1: 为什么不应该直接用Thread.sleep()代替join()?
A: sleep()仅能让出CPU时间片,但不会真正等待子线程结束,若子线程尚未完成,主线程可能在sleep()结束后提前继续执行,导致数据不一致,而join()会严格阻塞主线程直到目标线程终止,适合需要确保子线程完全执行完毕的场景。
Q2: CountDownLatch和CyclicBarrier有什么区别?
A: 两者都用于线程同步,但侧重点不同:
CountDownLatch是递减计数器,适用于“事件完成”模型(如等待N个任务全部完成)CyclicBarrier是可循环使用的屏障,适用于“回合制”场景(如每轮比赛开始前等待所有选手就位)CyclicBarrier还支持注册屏障动作(BarrierAction),可在屏障触发时执行
