Java从ExecutorService设置回调

时间:2012-10-29 05:14:17

标签: java multithreading callback threadpool executorservice

我有一个fixedThreadPool,我用它来运行一堆工作线程来实现具有许多组件的任务的并行执行。

当所有线程都完成后,我使用方法(getResult)检索它们的结果(非常大)并将它们写入文件。

最终,为了节省内存并能够看到中间结果,我希望每个线程在完成执行后立即将其结果写入文件,然后释放其内存。

通常,我会在run()方法的末尾添加代码。但是,此类中的某些其他对象也会调用这些线程,但不要让它们将结果写入文件 - 而是使用它们的结果执行其他计算,最终将其写入文件。

所以,我想知道是否可以将回调函数附加到使用ExecutorService完成线程的事件。这样,我可以立即检索其结果并在该场景中释放内存,但在其他场景中使用这些线程时不会破坏代码。

这样的事情可能吗?

6 个答案:

答案 0 :(得分:5)

ExecutorService#submit返回FutureTask<T>,它可以帮助您检索结果,ExecutorService#get方法将阻止执行,直到计算未完成。示例 -

ExecutorService executor = Executors.newFixedThreadPool(10);
Future<Long> future = executor.submit(new Callable<Long>(){
       @Override
       public Long call() throws Exception {
           long sum = 0;
           for (long i = 0; i <= 10000000l; i++) {
               sum += i;
           }
           return sum;
       }
});
Long result = future.get();
System.out.println(result);

答案 1 :(得分:5)

如果您选择使用Google Guava,则可以通过以下方式使用ListenableFuture界面:

  1. 通过ExecutorService
  2. MoreExecutors.listeningDecorator(existingExecutorService)转换为ListeningExecutorService
  3. submit(Callable<V>) ListeningExecutorService方法已缩小,以返回ListenableFutureFuture的子接口。
  4. ListenableFuture有一个addListener()方法,因此您可以注册一个回调,以便在未来完成时运行。

答案 2 :(得分:2)

  

所以,我想知道是否可以将回调函数附加到使用ExecutorService完成线程的事件。

不直接,不,但有几种方法可以实现这一目标。想到的最简单的方法是将Runnable包裹在收获结果的另一个Runnable中。

所以你会做类似的事情:

threadPool.submit(new ResultPrinter(myRunnable));
...

private static class ResultPrinter implements Runnable {
    private final MyRunnable myRunnable;
    public ResultPrinter(MyRunnable myRunnable) {
        this.myRunnable = myRunnable;
    }
    public void run() {
        myRunnable.run();
        Results results = myRunnable.getResults();
        // print results;
    }
}

答案 3 :(得分:1)

您可以使用CompletableFuture添加回调,以便使用t在Java 8+中返回,其中int x = 10; ExecutorService fixedThreadPool = Executors.newFixedThreadPool(x); CompletableFuture.supplyAsync(() -> { T t = new T(); // do something return t; }).thenApply(t -> { // process t } 是长时间运行计算的结果,

int x = 10;
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(x);
Future<T> result = fixedThreadPool.submit(() -> {
    // do calculation
    return T;
});
fixedThreadPool.submit(() -> {
    long minutesToWait = 5;
    T t = null;
    try {
        t = result.get(minutesToWait, TimeUnit.MINUTES);
    } catch (InterruptedException | ExecutionException | TimeoutException e) {
        LOGGER.error(e);
    }
    if (t != null) {
        // process t
    }
});

如果你想在Java 7中使用回调,你可以做类似的事情,

{{1}}

答案 4 :(得分:0)

项目织机

Project Loom 有望为 Java 的并发工具带来新功能。实验版本 available now,基于早期访问的 Java 17。Loom 团队正在征求反馈。有关更多信息,请参阅团队成员(如 Ron Pressler 或 Alan Bateman)的任何最新视频和文章。 Loom 已经发展,所以研究最新的资源。

Project Loom 的一项便利功能是将 ExecutorService 设为 AutoCloseable。这意味着我们可以使用 try-with-resources 语法来自动关闭执行程序服务。控制流在 try 块的末尾阻塞,直到所有提交的任务都完成/失败/取消。之后,执行器服务会自动关闭。简化我们的代码,并通过可视化的代码结构使我们等待任务完成的意图变得明显。

Project Loom 的另一个导入功能是虚拟线程(也称为 fibers)。虚拟线程在内存和 CPU 方面都是轻量级的。

  • 关于内存,每个虚拟线程都会获得一个可根据需要增长和缩小的堆栈。
  • 就 CPU 而言,许多虚拟线程中的每一个都位于多个平台/内核线程中的任何一个之上。这使得阻塞非常便宜。当一个虚拟线程阻塞时,它会被“停放”(搁置),以便另一个虚拟线程可以继续在“真实”平台/内核线程上执行。

轻量级意味着我们可以同时拥有多个虚拟线程,甚至数百万个。

➥ 你的问题的挑战是当提交的任务准备好返回其结果时立即做出反应,而不是等待所有其他任务完成。有了 Project Loom 技术,这件事就简单多了。

只需在另一个线程上的每个 get 上调用 Future

因为我们有几乎无穷多的线程,而且因为阻塞非常便宜,所以我们可以提交一个任务,只需调用 Future#get 以等待每个 {{} 返回的每个 Future 的结果3}} 我们提交给执行程序服务。对 get 的调用会阻塞,直到它来自的 Callable 完成工作并返回结果。

通常,我们希望避免将 Future#get 调用分配给传统的后台线程。该线程将停止所有进一步的工作,直到被阻塞的 get 方法返回。但是对于 Loom 项目,该阻塞调用被检测到,并且其线程被“停放”,因此其他线程可能会继续。当阻塞调用最终返回时,Loom 也会检测到它,导致不再阻塞任务的虚拟线程很快被安排在“真实”线程上进一步执行。所有这些停放和重新安排都快速且自动地发生,我们 Java 程序员无需付出任何努力。

为了演示,我的任务的结果被塞进了一个并发映射中。为了表明这会在结果可用时立即发生,我重写了 put 类上的 ConcurrentSkipListMap 方法以执行 System.out.println 消息。

完整的示例应用如下所示。但 3 条关键线如下。请注意我们如何实例化一个休眠几秒钟的 Callable,然后将当前时刻作为 Callable 对象返回。当我们提交每个 Callable 对象时,我们会返回一个 Future 对象。对于每个返回的 Future,我们将另一个任务 Runnable 提交给我们相同的执行程序服务,该服务仅调用 Future#get,等待结果,并最终将该结果发布到我们的结果映射中。

final Callable < Instant > callable = new TimeTeller( nth );
final Future < Instant > future = executorService.submit( callable ); // Submit first task: a `Callable`, an instance of our `TimeTeller` class.
executorService.submit( ( ) -> results.put( nth , future.get() ) );   // Submit second task: a `Runnable` that merely waits for our first task to finish, and put its result into a map.

警告:我不是并发方面的专家。但我相信我的方法是合理的。

警告:Loom 项目仍处于试验阶段,其 API 和行为可能会发生变化。

package work.basil.example.callbacks;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.*;

public class App
{
    public static void main ( String[] args )
    {
        App app = new App();
        app.demo();
    }

    private void demo ( )
    {
        System.out.println( "INFO - Starting `demo` method. " + Instant.now() );
        int limit = 10;
        ConcurrentNavigableMap < Integer, Instant > results = new ConcurrentSkipListMap <>()
        {
            @Override
            public Instant put ( Integer key , Instant value )
            {
                System.out.println( "INFO - Putting key=" + key + " value=" + value + " at " + Instant.now() );
                return super.put( key , value );
            }
        };
        try (
                ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
        )
        {
            for ( int i = 0 ; i < limit ; i++ )
            {
                final Integer nth = Integer.valueOf( i );
                final Callable < Instant > callable = new TimeTeller( nth );
                final Future < Instant > future = executorService.submit( callable ); // Submit first task: a `Callable`, an instance of our `TimeTeller` class.
                executorService.submit( ( ) -> results.put( nth , future.get() ) );   // Submit second task: a `Runnable` that merely waits for our first task to finish, and put its result into a map.
            }
        }
        // At this point flow-of-control blocks until:
        // (a) all submitted tasks are done/failed/canceled, and
        // (b) the executor service is automatically closed.
        System.out.println( "INFO - Ending `demo` method. " + Instant.now() );
        System.out.println( "limit = " + limit + " | count of results: " + results.size() );
        System.out.println( "results = " + results );
    }

    record TimeTeller(Integer id) implements Callable
    {
        @Override
        public Instant call ( ) throws Exception
        {
            // To simulate work that involves blocking, sleep a random number of seconds.
            Duration duration = Duration.ofSeconds( ThreadLocalRandom.current().nextInt( 1 , 55 ) );
            System.out.println( "id = " + id + " ➠ duration = " + duration );
            Thread.sleep( duration );
            return Instant.now();
        }
    }
}

运行时。

INFO - Starting `demo` method. 2021-03-07T07:51:03.406847Z
id = 1 ➠ duration = PT27S
id = 2 ➠ duration = PT4S
id = 4 ➠ duration = PT6S
id = 5 ➠ duration = PT16S
id = 6 ➠ duration = PT34S
id = 7 ➠ duration = PT33S
id = 8 ➠ duration = PT52S
id = 9 ➠ duration = PT17S
id = 0 ➠ duration = PT4S
id = 3 ➠ duration = PT41S
INFO - Putting key=2 value=2021-03-07T07:51:07.443580Z at 2021-03-07T07:51:07.444137Z
INFO - Putting key=0 value=2021-03-07T07:51:07.445898Z at 2021-03-07T07:51:07.446173Z
INFO - Putting key=4 value=2021-03-07T07:51:09.446220Z at 2021-03-07T07:51:09.446623Z
INFO - Putting key=5 value=2021-03-07T07:51:19.443060Z at 2021-03-07T07:51:19.443554Z
INFO - Putting key=9 value=2021-03-07T07:51:20.444723Z at 2021-03-07T07:51:20.445132Z
INFO - Putting key=1 value=2021-03-07T07:51:30.443793Z at 2021-03-07T07:51:30.444254Z
INFO - Putting key=7 value=2021-03-07T07:51:36.445371Z at 2021-03-07T07:51:36.445865Z
INFO - Putting key=6 value=2021-03-07T07:51:37.442659Z at 2021-03-07T07:51:37.443087Z
INFO - Putting key=3 value=2021-03-07T07:51:44.449661Z at 2021-03-07T07:51:44.450056Z
INFO - Putting key=8 value=2021-03-07T07:51:55.447298Z at 2021-03-07T07:51:55.447717Z
INFO - Ending `demo` method. 2021-03-07T07:51:55.448194Z
limit = 10 | count of results: 10
results = {0=2021-03-07T07:51:07.445898Z, 1=2021-03-07T07:51:30.443793Z, 2=2021-03-07T07:51:07.443580Z, 3=2021-03-07T07:51:44.449661Z, 4=2021-03-07T07:51:09.446220Z, 5=2021-03-07T07:51:19.443060Z, 6=2021-03-07T07:51:37.442659Z, 7=2021-03-07T07:51:36.445371Z, 8=2021-03-07T07:51:55.447298Z, 9=2021-03-07T07:51:20.444723Z}

答案 5 :(得分:-1)

糟糕,刚刚在StackOverflow上找到答案:

Java ExecutorService callback on thread terminate

感谢您的耐心等待!