java并发中批量任务的同步和管理

ExecutorService

ExecutorService 是 Java 并发库中的一个接口,它提供了一种管理和控制线程池的方式,用于执行和管理多个异步任务。通过使用 ExecutorService,您可以更方便地提交、执行和管理线程任务,而无需直接操作线程的创建和管理。

ExecutorService 接口定义了一些用于操作线程池的方法,包括提交任务、关闭线程池、等待任务完成等。它提供了不同类型的线程池实现,比如固定大小线程池、缓存线程池、单线程池等,以满足不同的并发需求。

1、invokeAny

**invokeAny()**用于提交一批任务,并等待其中任意一个任务完成(返回结果或抛出异常)。返回的是第一个成功完成的任务的结果。

这个方法在需要并行执行多个任务,但只关心最快完成的任务结果的场景下非常有用。如果有多个任务都已经完成,那么只返回其中一个任务的结果。

以下是一个使用 invokeAny() 方法的简单示例:

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

public class InvokeAnyExample {
    
    
    public static void main(String[] args) {
    
    
        //项目中要使用自定义线程池,不要使用Executors
        ExecutorService executor = Executors.newFixedThreadPool(5);

        List<Callable<Integer>> tasks = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
    
    
            final int taskId = i;
            tasks.add(() -> {
    
    
                // 模拟不同耗时的任务
                Thread.sleep(1000);
                return taskId;
            });
        }

        try {
    
    
            int result = executor.invokeAny(tasks);

            System.out.println("First completed task result: " + result);
        } catch (InterruptedException | ExecutionException e) {
    
    
            e.printStackTrace();
        }

        executor.shutdown();
    }
}

运行结果

First completed task result: 3

首先创建了一个固定大小的线程池 executor,然后创建了一个包含多个 Callable 任务的列表 tasks。每个任务模拟了一个耗时操作,耗时逐渐增加。接着,使用 executor.invokeAny(tasks) 提交任务并等待其中任意一个任务完成,返回第一个成功完成的任务的结果。

需要注意的是,invokeAny() 方法会阻塞当前线程,直到至少有一个任务完成。如果所有任务都失败或抛出异常,它会抛出 ExecutionException 异常。在实际使用中,根据需求和任务的性质选择合适的方法来处理并行任务。

以下是一个使用 invokeAll() 方法的简单示例:

package com.lf.java.basic.concurrent;

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

public class InvokeAllExample {
    
    
    public static void main(String[] args) {
    
    
        //项目中要使用自定义线程池,不要使用Executors
        ExecutorService executor = Executors.newFixedThreadPool(5);

        List<Callable<Integer>> tasks = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
    
    
            final int taskId = i;
            tasks.add(() -> {
    
    
                // 模拟耗时任务
                Thread.sleep(1000);
                return taskId;
            });
        }

        try {
    
    
            List<Future<Integer>> results = executor.invokeAll(tasks);

            // 处理任务结果
            for (Future<Integer> result : results) {
    
    
                try {
    
    
                    int taskId = result.get();
                    System.out.println("Task " + taskId + " completed.");
                } catch (InterruptedException | ExecutionException e) {
    
    
                    e.printStackTrace();
                }
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        executor.shutdown();
    }
}

运行结果

Task 1 completed.
Task 2 completed.
Task 3 completed.
Task 4 completed.
Task 5 completed.
Task 6 completed.
Task 7 completed.
Task 8 completed.
Task 9 completed.
Task 10 completed.

在上述示例中,首先创建了一个固定大小的线程池 executor,然后创建了一个包含多个 Callable 任务的列表 tasks。每个任务都模拟了一个耗时操作,并返回任务的标识。接着,我们使用 executor.invokeAll(tasks) 提交任务并等待它们全部完成,返回一个包含 Future 对象的列表。

最后,我们遍历 results 列表,使用 Future.get() 方法来获取每个任务的结果,并处理任务完成后的逻辑,会按照加入的任务顺序返回结果

需要注意的是,invokeAll() 方法会阻塞当前线程,直到所有任务完成。如果任务在完成之前被中断,它会抛出 InterruptedException 异常。在实际使用中,根据任务的性质和需求合理选择使用 invokeAll() 或其他方式来提交和处理任务。

2、invokeAll

invokeAll(tasks) 是 Java 并发库中 ExecutorService 接口提供的一个方法,用于提交一批任务并等待它们全部完成。返回的是一个包含 Future 对象的列表,每个 Future 对象代表一个任务的执行结果。
这个方法通常在需要同时提交多个任务并等待它们全部完成的场景下使用,比如批量处理、并行计算等。

ExecutorCompletionService

ExecutorCompletionService是Java中的一个实用类,它实现了基于Executor的一种并发模式,用于管理异步任务的执行和结果获取。它允许将任务提交给一个Executor进行并发执行,并且可以按照任务完成的顺序获取它们的结果。

在多线程并发编程中,ExecutorCompletionService可以用来优雅地处理一组异步任务的结果,尤其适用于需要等待多个任务完成并获取结果的场景。

1、Executors ExecutorCompletionService

示例代码:

package com.lf.java.basic.concurrent;

import java.util.concurrent.*;

public class ExecutorCompletionServiceExample {
    
    

    public static void main(String[] args) {
    
    
        //项目中要使用自定义线程池,不要使用Executors
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ExecutorCompletionService<Integer> completionService = new ExecutorCompletionService<>(executorService);

        // 提交一些任务
        for (int i = 1; i <= 10; i++) {
    
    
            final int taskId = i;
            completionService.submit(() -> {
    
    
                // 模拟耗时任务
                Thread.sleep(1000);
                return taskId;
            });
        }

        // 获取任务结果
        try {
    
    
            for (int i = 1; i <= 10; i++) {
    
    
                Future<Integer> completedTask = completionService.take();
                int result = completedTask.get();
                System.out.println("Task " + result + " completed.");
            }
        } catch (InterruptedException | ExecutionException e) {
    
    
            e.printStackTrace();
        }

        // 关闭ExecutorService
        executorService.shutdown();
    }
}

运行结果:

Task 3 completed.
Task 2 completed.
Task 1 completed.
Task 4 completed.
Task 5 completed.
Task 6 completed.
Task 7 completed.
Task 8 completed.
Task 9 completed.
Task 10 completed.

首先创建了一个固定大小的线程池executorService,然后创建了一个ExecutorCompletionService,将executorService传递给它。接着,我们向completionService提交了一组任务,这些任务会被并发执行。

使用completionService.take()方法可以从完成的任务队列中获取已经完成的任务,这个方法会阻塞,直到有任务完成。然后,我们可以通过获取的Future对象来获取任务的结果。

需要注意的是,ExecutorCompletionService会按照任务完成的顺序返回结果,而不是按照提交的顺序。这使得你可以更容易地处理已经完成的任务结果,而无需等待所有任务都完成

调用 executorService.shutdown() 时,线程池会开始停止接受新任务,并尝试将已提交但尚未执行的任务执行完成。已经在执行的任务会继续执行,但不再接受新任务。一旦所有任务都完成,线程池会完全关闭并释放资源。

如果去掉 executorService.shutdown(),线程池不会被关闭,而是会继续等待新的任务并执行已经提交的任务。这可能会导致程序在没有显示关闭线程池的情况下一直运行下去。

资源泄漏: 由于线程池没有被关闭,线程资源不会被释放,可能导致系统资源的浪费。

程序无法正常退出: 因为线程池中的线程一直在运行,主程序无法顺利退出。

要确保程序的正常退出和资源的释放,建议在不需要使用线程池时,及时调用 executorService.shutdown() 来关闭线程池。这将会终止线程池中的所有线程,并释放相关的资源。如果你想要等待线程池中的任务全部执行完毕后再关闭线程池,可以使用 awaitTermination() 方法来等待。

2、自定义线程池ExecutorCompletionService

自定义线程池

package com.lf.java.basic.concurrent;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class WhiteListThreadPool {
    
    

    private WhiteListThreadPool() {
    
    
    }

    public static final ExecutorService executorService = new ThreadPoolExecutor(
             //机器核数
            Runtime.getRuntime().availableProcessors() + 1,
            Runtime.getRuntime().availableProcessors() * 2 + 1,
            0L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1000),
            new NameTreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    static class NameTreadFactory implements ThreadFactory {
    
    

        private final AtomicInteger threadNum = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
    
    
            Thread t = new Thread(r, "white-list-worker" + threadNum.getAndIncrement());
            log.info(t.getName() + "create...");
            return t;
        }
    }

}


ExecutorCompletionServiceExample使用自定义线程池

package com.lf.java.basic.concurrent;

import java.util.concurrent.*;

public class ExecutorCompletionServiceExample {
    
    

    public static void main(String[] args) {
    
    
        
        ExecutorService executorService = WhiteListThreadPool.executorService;
        ExecutorCompletionService<Integer> completionService = new ExecutorCompletionService<>(executorService);

        // 提交一些任务
        for (int i = 1; i <= 10; i++) {
    
    
            final int taskId = i;
            completionService.submit(() -> {
    
    
                // 模拟耗时任务
                Thread.sleep(1000);
                return taskId;
            });
        }

        // 获取任务结果
        try {
    
    
            for (int i = 1; i <= 10; i++) {
    
    
                Future<Integer> completedTask = completionService.take();
                int result = completedTask.get();
                System.out.println("Task " + result + " completed.");
            }
        } catch (InterruptedException | ExecutionException e) {
    
    
            e.printStackTrace();
        }

        // 自定义线程池一般不需要关闭ExecutorService
//        executorService.shutdown();
    }
}

CompletableFuture

CompletableFuture 是 Java 并发库中提供的一个类,用于处理异步任务的结果和操作。它提供了一种更为灵活和强大的方式来处理异步编程,能够轻松实现复杂的异步任务组合、转换和操作。
下是一些 CompletableFuture 的重要特性和用法:

  1. 异步任务提交:通过 CompletableFuture.supplyAsync(Supplier supplier) 或
    CompletableFuture.runAsync(Runnable runnable)方法,可以提交异步任务。supplyAsync 返回一个带有结果的 CompletableFuture,runAsync返回一个不带结果的 CompletableFuture。

  2. 串联操作:使用一系列的 then 方法可以将多个异步任务串联在一起,形成操作流程。例如,thenApply, thenAccept,
    thenRun 用于处理结果,thenCompose, thenCombine, thenCombineAsync 用于组合多个任务。

  3. 转换和映射:通过 thenApply, thenCompose
    等方法,可以对任务的结果进行转换和映射。这对于对异步任务的结果进行处理、过滤、转换等操作非常有用。

  4. 组合操作:通过 thenCombine, thenCombineAsync, thenCompose
    等方法,可以组合多个任务的结果,从而构建更复杂的操作流程。

  5. 异常处理:通过 exceptionally(Function<Throwable, T> function) 和
    handle(BiFunction<T, Throwable, T> function)方法,可以处理任务执行过程中的异常情况,并返回默认值或处理结果。

  6. 等待和获取结果:通过 join() 或 get() 方法,可以等待任务完成并获取结果。join() 方法没有异常抛出,而 get()方法可能会抛出异常。

  7. 等待多个任务完成:使用 CompletableFuture.allOf(CompletableFuture<?>... cfs) 可以等待多个任务全部完成,或使用 CompletableFuture.anyOf(CompletableFuture<?>…
    cfs) 等待任意一个任务完成

  8. 回调机制:通过 thenAccept, thenRun, whenComplete, exceptionally
    等方法,可以添加回调来处理任务完成、结果处理以及异常情况。

  9. 异步执行:CompletableFuture 提供了许多异步执行的方法,可以使用指定的线程池来执行任务,例如
    thenApplyAsync, thenAcceptAsync, thenRunAsync。

  10. 取消任务:使用 cancel(boolean mayInterruptIfRunning) 方法可以尝试取消任务的执行。

需要注意的是,CompletableFuture 提供了更高级和更灵活的异步编程方式,适用于构建复杂的异步任务流程和操作。它的强大功能可以有效地提高代码的可读性和维护性,同时也需要注意合理处理异常、线程资源和线程池的管理。

1、提交批量任务并处理结果

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureBatchExample {
    
    
    public static void main(String[] args) {
    
    
        List<CompletableFuture<Integer>> futures = new ArrayList<>();

        for (int i = 1; i <= 10; i++) {
    
    
            int taskId = i;
            CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
    
    
                // 模拟耗时任务
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                return taskId;
            });
            futures.add(future);
        }

        CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

        CompletableFuture<List<Integer>> resultFuture = allOf.thenApplyAsync(v ->
                futures.stream()
                        .map(CompletableFuture::join)
                        .collect(Collectors.toList())
        );

        try {
    
    
            List<Integer> results = resultFuture.get();
            System.out.println("Results: " + results);
        } catch (InterruptedException | ExecutionException e) {
    
    
            e.printStackTrace();
        }
    }
}

运行结果:

Results: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

在这个示例中,首先创建了一个包含多个 CompletableFuture 对象的列表 futures,每个对象代表一个异步任务。然后,我们使用 CompletableFuture.allOf() 等待所有任务完成。接着,通过 thenApplyAsync() 方法处理所有任务的结果,将每个任务的结果收集到一个列表中。

2、组合任务和异常处理:

import java.util.concurrent.CompletableFuture;

public class CompletableFutureComposeExample {
    
    
    public static void main(String[] args) {
    
    
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20);

        CompletableFuture<Integer> combinedFuture = future1.thenCombineAsync(future2, (result1, result2) -> result1 + result2);

        combinedFuture
                .exceptionally(ex -> {
    
    
                    System.out.println("Exception: " + ex.getMessage());
                    return 0;
                })
                .thenAccept(result -> {
    
    
                    System.out.println("Combined Result: " + result);
                });

        combinedFuture.join();
    }
}

运行结果:

Combined Result: 30

在这个示例中,创建了两个 CompletableFuture 对象 future1future2,然后使用 thenCombineAsync() 方法将它们的结果相加。同时,我们使用 exceptionally() 处理异常情况,并使用 thenAccept() 处理任务结果。

通过这些灵活的操作和链式处理,你可以创建更复杂的异步任务流程,实现更精细的任务同步和管理。

Guava ListenableFuture

ListenableFuture 是 Google Guava 库中提供的一个接口,用于处理异步任务的结果和监听。它是对标准 Java 的 Future 接口的扩展,提供了更强大和灵活的功能,特别是在异步任务完成时的监听和回调方面。

ListenableFuture 的主要优点是可以注册监听器,在异步任务完成时自动触发回调,避免了手动轮询或阻塞线程等待任务完成。这对于并发编程和异步操作非常有用,可以自定义对成功、失败的处理

代码示例:

import com.google.common.util.concurrent.*;

import java.util.concurrent.Executors;

public class ListenableFutureExample {
    
    
    public static void main(String[] args) {
    
    
        ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5));

        ListenableFuture<Integer> future = executorService.submit(() -> {
    
    
            // 模拟耗时任务
            Thread.sleep(1000);
            return 42;
        });

        Futures.addCallback(future, new FutureCallback<Integer>() {
    
    
            @Override
            public void onSuccess(Integer result) {
    
    
                System.out.println("Task completed. Result: " + result);
            }

            @Override
            public void onFailure(Throwable throwable) {
    
    
                System.out.println("Task failed: " + throwable.getMessage());
            }
        }, executorService);

        executorService.shutdown();
    }
}

运行结果:

Task 3 completed successfully.
Task 1 completed successfully.
Task 2 completed successfully.
Task 0 completed successfully.
Task 4 completed successfully.

在上述示例中,我们首先创建了一个 ListeningExecutorService,然后提交了一些异步任务给它。接着,我们通过迭代 listenableFutures 列表,为每个 ListenableFuture 添加监听器,使用 Futures.addCallback() 方法。

监听器实现了 FutureCallback 接口,包括 onSuccess() 和 onFailure() 方法,分别在任务成功完成和失败时触发。

最后,我们使用 Futures.allAsList() 来等待所有任务完成,然后关闭线程池。

需要注意,Guava 的 ListenableFuture 可以更方便地处理异步任务的监听,但需要引入 Guava 库。确保适当地处理异常以及线程池的关闭是非常重要的。

总结:

在并发编程中,有多种方式可以实现批量任务的同步和管理。
以下是一些常见的方式,以及它们之间的区别:

1、ExecutorCompletionService:

  1. 使用 ExecutorCompletionService 可以提交一批任务,并在它们完成时按照完成的顺序处理结果。
  2. 适用于需要按照任务完成的顺序处理结果的场景。
  3. 可以在任务完成时立即处理结果,不需要等待所有任务都完成。
  4. 使用 take() 方法从队列中获取已完成的任务,或者使用 poll() 方法进行非阻塞获取。

2、CompletableFuture:

  1. 使用 CompletableFuture 可以以异步方式提交一批任务,并在它们完成时执行一些操作。
  2. 提供了更灵活的操作,如链式操作、组合操作、异常处理等。
  3. 可以通过 thenApply(), thenAccept(), thenRun(), thenCompose() 等方法串联操作。
  4. 可以使用 allOf() 等方法等待所有任务完成,也可以使用 anyOf() 等方法等待任意一个任务完成。
  5. 可以通过添加回调或使用 whenComplete(), handle(), exceptionally() 等方法处理任务结果和异常。

3、ListenableFuture:

  1. 使用 ListenableFuture(如 Guava 的 ListenableFuture 或 Spring 的
    ListenableFuture)可以添加监听器来处理任务完成时的操作。
  2. 提供了类似于回调的方式来处理任务结果和异常。 可以使用 addCallback() 方法添加回调,分别处理成功和失败情况。

4、invokeAll() 和 invokeAny():

  1. 使用 invokeAll() 方法可以提交一组任务,并等待它们全部完成。
  2. 使用 invokeAny() 方法可以提交一组任务,并等待其中任意一个完成。
  3. invokeAll() 返回一个包含 Future 对象的列表,可以逐个获取任务的结果。
  4. invokeAny() 返回第一个成功完成的任务的结果。

5、结合需求选择

  1. 如果需要按照完成顺序处理结果,可以使用 ExecutorCompletionService。
  2. 如果需要更灵活的操作和链式处理,可以选择 CompletableFuture。
  3. 如果你已经在使用 Guava 或 Spring,ListenableFuture 可能是一个不错的选择。
  4. 而 invokeAll() 和 invokeAny() 则适用于一次性提交多个任务并等待结果的场景。

猜你喜欢

转载自blog.csdn.net/FLGBgo/article/details/132168452
今日推荐