我有一个fixedThreadPool,我用它来运行一堆工作线程来实现具有许多组件的任务的并行执行。
当所有线程都完成后,我使用方法(getResult)检索它们的结果(非常大)并将它们写入文件。
最终,为了节省内存并能够看到中间结果,我希望每个线程在完成执行后立即将其结果写入文件,然后释放其内存。
通常,我会在run()方法的末尾添加代码。但是,此类中的某些其他对象也会调用这些线程,但不要让它们将结果写入文件 - 而是使用它们的结果执行其他计算,最终将其写入文件。
所以,我想知道是否可以将回调函数附加到使用ExecutorService完成线程的事件。这样,我可以立即检索其结果并在该场景中释放内存,但在其他场景中使用这些线程时不会破坏代码。
这样的事情可能吗?
答案 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界面:
ExecutorService
MoreExecutors.listeningDecorator(existingExecutorService)
转换为ListeningExecutorService
submit(Callable<V>)
ListeningExecutorService
方法已缩小,以返回ListenableFuture
,Future
的子接口。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 方面都是轻量级的。
轻量级意味着我们可以同时拥有多个虚拟线程,甚至数百万个。
➥ 你的问题的挑战是当提交的任务准备好返回其结果时立即做出反应,而不是等待所有其他任务完成。有了 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)