java怎么让主线程等待

java怎么让主线程等待

在Java中,可使用Thread.sleep(long millis 使主线程休眠指定毫秒数,或调用其他线程的`...

优惠价格:¥ 0.00
当前位置:首页 > 后端开发 > java怎么让主线程等待
详情介绍
在Java中,可使用 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关键字使用,适用于复杂的条件等待场景。

关键步骤:

  1. 获取对象的监视器锁(synchronized(obj)
  2. 调用obj.wait()进入等待集
  3. 其他线程修改共享变量后,调用obj.notify()/notifyAll()唤醒等待线程
  4. 被唤醒的线程重新竞争锁并继续执行

代码示例:

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: CountDownLatchCyclicBarrier有什么区别?

A: 两者都用于线程同步,但侧重点不同:

  • CountDownLatch是递减计数器,适用于“事件完成”模型(如等待N个任务全部完成)
  • CyclicBarrier是可循环使用的屏障,适用于“回合制”场景(如每轮比赛开始前等待所有选手就位)
  • CyclicBarrier还支持注册屏障动作(BarrierAction),可在屏障触发时执行
0