等待线程池中任务执行完成

博客主要提及Callable Task返回Feature可判断isDone(),并对ThreadPoolExecutor线程池submit无返回值的Runnable task进行测试,看其是否也能返回Feature判断isDone()。

Callable Task本身返回Feature可判断isDone(),在此不赘述。

测试 ThreadPoolExecutor 线程池submit无返回值的Runnable task也可返回Feature判断isDone()

package com.example.test;

import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class t1 {
  @Test
  public void test1() {
    ThreadPoolExecutor pool =
        new ThreadPoolExecutor(
            2,
            4,
            0,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());

    Future<?> submit = pool.submit(new MyTask());
    while (!submit.isDone()) {
      System.out.println(submit.isDone());
    }
    System.out.println(submit.isDone());
  }

  private class MyTask implements Runnable {

    @Override
    public void run() {
      try {
        Thread.sleep(10000);
        System.out.println("task1 completed");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

结果:

...
false
false
task1 completed
false
false
false
true
# 10s44ms
<think>我们面临的问题是:主线程需要等待线程池中特定的任务(而不是所有任务完成后再继续执行。注意,线程池中可能还有其他任务在运行,但我们只关心特定的任务。 根据引用[1]和[3],我们可以使用CountDownLatch来实现等待特定任务完成。但是,这里需要注意的是,CountDownLatch的计数器应该只针对我们关心的特定任务。 另外,引用[2]中提到,使用join()方法对于线程池中的线程不可行,因为线程池中的线程不会结束(线程复用)或者我们无法直接获取到线程的引用。因此,使用CountDownLatch是更合适的方式。 解决方案: 1. 创建CountDownLatch对象,计数器初始化为我们需要等待的特定任务的数量(比如1个或多个特定任务)。 2. 在提交特定任务线程池时,在任务执行完成后(无论成功还是异常)调用countDown()方法。 3. 主线程调用await()方法等待这些特定任务完成。 但是,注意:线程池中可能还有其他任务,我们不需要等待它们。因此,我们只为特定的任务使用CountDownLatch。 示例代码: 假设我们有一个线程池,我们提交了多个任务,但只关心其中两个任务(task1和task2)是否完成。 步骤: 1. 创建CountDownLatch,计数器为2(因为我们等待两个任务)。 2. 提交任务时,为这两个特定任务任务内部添加countDown()。 3. 主线程在需要的地方调用latch.await()。 代码示例: ```java import java.util.concurrent.*; public class WaitSpecificTasks { public static void main(String[] args) throws InterruptedException { // 创建线程池 ThreadPoolExecutor executor = new ThreadPoolExecutor( 3, 3, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); // 创建CountDownLatch,计数器为2(因为我们只关心两个任务) CountDownLatch latch = new CountDownLatch(2); // 提交多个任务,其中两个特定任务我们使用latch for (int i = 0; i < 5; i++) { final int taskId = i; // 如果是特定任务(比如任务0和任务1),则使用latch if (taskId == 0 || taskId == 1) { executor.submit(() -> { try { System.out.println("特定任务" + taskId + "开始执行"); Thread.sleep(2000); // 模拟任务执行 System.out.println("特定任务" + taskId + "执行完成"); } catch (InterruptedException e) { e.printStackTrace(); } finally { latch.countDown(); // 确保无论如何都会减少计数 } return null; }); } else { // 其他任务不需要使用latch executor.submit(() -> { System.out.println("普通任务" + taskId + "执行"); return null; }); } } // 主线程等待两个特定任务完成 latch.await(); System.out.println("主线程继续执行,因为两个特定任务已经完成"); // 关闭线程池(注意:这里只是不再接受新任务,已提交的任务会继续执行完) executor.shutdown(); } } ``` 注意:这里我们只等待任务0和任务1,其他任务任务2、3、4)的执行不会影响主线程的继续执行。 另外,引用[3]中提到,我们也可以使用线程池的awaitTermination()方法,但是这个方法会等待所有任务完成,而不是特定任务。所以在这里不适用。 如果我们需要等待的特定任务数量是动态的,那么我们可以根据任务数量初始化CountDownLatch。 总结:使用CountDownLatch,在特定任务完成时调用countDown(),主线程调用await()等待,这样就能实现主线程等待线程池中特定任务完成。 引用[1]中展示了在任务中使用CountDownLatch的示例,并在finally块中调用countDown(),确保即使任务异常也会减少计数,避免主线程一直等待。 因此,我们采用CountDownLatch方案。 相关问题: 1. 如果特定任务执行时间很长,如何设置超时等待? 2. 如何确保CountDownLatch不被错误地多次调用countDown()? 3. 除了CountDownLatch,还有哪些方式可以实现等待特定任务完成?</think>在 Java 中,主线程等待线程池中**特定任务**完成后继续执行,可以使用 `CountDownLatch` 或 `CompletableFuture` 实现精准控制。以下是两种推荐方案: --- ### 方案一:使用 `CountDownLatch`(精准控制特定任务) ```java ExecutorService executor = Executors.newFixedThreadPool(5); CountDownLatch latch = new CountDownLatch(3); // 只等待3个特定任务 // 提交普通任务(无需等待) executor.submit(() -> System.out.println("普通任务1")); executor.submit(() -> System.out.println("普通任务2")); // 提交需要等待的特定任务 executor.submit(() -> { try { System.out.println("特定任务A执行"); Thread.sleep(2000); } finally { latch.countDown(); // 完成后计数器减1 } }); executor.submit(() -> { try { System.out.println("特定任务B执行"); Thread.sleep(1500); } finally { latch.countDown(); } }); executor.submit(() -> { try { System.out.println("特定任务C执行"); Thread.sleep(1000); } finally { latch.countDown(); } }); // 主线程等待3个特定任务完成 latch.await(); System.out.println("主线程继续:特定任务A/B/C已完成"); // 关闭线程池(可选) executor.shutdown(); ``` **关键点**: 1. 只对需要等待的特定任务调用 `countDown()`[^1] 2. `finally` 块确保异常时也能触发计数 3. 普通任务不受影响,可自由执行 4. 支持超时等待:`latch.await(5, TimeUnit.SECONDS)` --- ### 方案二:使用 `CompletableFuture`(Java 8+ 推荐) ```java ExecutorService executor = Executors.newFixedThreadPool(5); // 提交普通任务 executor.submit(() -> System.out.println("普通任务")); // 创建特定任务的Future CompletableFuture<Void> taskA = CompletableFuture.runAsync(() -> { System.out.println("特定任务A执行"); sleep(2000); }, executor); CompletableFuture<Void> taskB = CompletableFuture.runAsync(() -> { System.out.println("特定任务B执行"); sleep(1500); }, executor); // 主线程等待特定任务完成 CompletableFuture.allOf(taskA, taskB).join(); System.out.println("主线程继续:任务A/B已完成"); executor.shutdown(); ``` **优势**: 1. 链式调用:轻松组合多个任务 2. 结果聚合:支持 `thenCombine()` 处理任务结果 3. 异常处理:通过 `exceptionally()` 捕获特定任务异常 --- ### 方案对比 | **场景** | `CountDownLatch` | `CompletableFuture` | |-----------------------------|--------------------------------|--------------------------------| | 精准控制任务数量 | ✅ 直观 | ⚠️ 需手动维护Future集合 | | 任务结果处理 | ❌ 不支持 | ✅ 支持返回值组合 | | 异常处理 | ⚠️ 需在任务内部处理 | ✅ 专用异常处理API | | 超时控制 | ✅ `await(timeout, unit)` | ✅ `orTimeout(timeout, unit)` | | 任务依赖关系 | ❌ 不支持 | ✅ 支持`thenRun()`等链式调用 | > **最佳实践**: > 1. 对**无返回值**的简单任务,优先用 `CountDownLatch` > 2. 对**需要结果聚合**或**链式调用**的任务,用 `CompletableFuture` > 3. 始终在 `finally` 块中调用 `countDown()` 避免死锁[^1] --- ### 注意事项 1. **避免过度阻塞**:主线程等待时间过长时,添加超时限制 ```java if (!latch.await(10, TimeUnit.SECONDS)) { System.err.println("特定任务执行超时"); } ``` 2. **线程池隔离**:重要任务使用独立线程池,避免被普通任务阻塞 3. **资源释放**:等待完成后调用 `shutdown()` 回收线程资源[^3] 通过以上方案,可实现主线程精准等待线程池中**特定任务**完成,同时允许其他任务自由执行。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值