当您将 Runnable/Callable 对象提交给执行器服务时,您会得到一个 Future 对象。您可以跟踪那些Future 对象,并询问它们的状态。每个人都会报告它是否被取消或完成。
全部完成后,向执行器服务提交另一批 Runnable/Callable 任务。
你可以在后台线程上运行这个Future-checker batch-submitter 作为另一个任务,使用ScheduledExecutorService 重复执行。不直接涉及主线程。
除此之外,我建议您检查您的假设。显然,您担心实例化数百万个 Runnable/Callable 对象,以免耗尽内存。但我怀疑每个 Runnable/Callable 对象以及生成的 Future 对象都占用大量内存。我建议您运行模拟来查看并使用监视器或分析工具检查内存使用情况。
这是一些示例代码。首先是我的Callable。
package work.basil.example;
import java.util.concurrent.Callable;
public record Enrichment(Integer id) implements Callable
{
@Override
public Boolean call ( ) throws Exception
{
System.out.println( this.toString() );
return Boolean.TRUE; // Report success.
}
}
还有一些代码可以预定 1000 万个可运行实例,并累积提交到执行器服务时产生的每个 Future 对象。
Instant start = Instant.now();
System.out.println( "INFO - Start running demo at " + start );
int limit = 10_000_000;
List < Future > futures = new ArrayList <>( limit );
ExecutorService executorService = null;
try
{
executorService = Executors.newFixedThreadPool( 3 );
for ( int i = 1 ; i <= limit ; i++ )
{
Callable < Boolean > callable = new Enrichment( i );
Future < Boolean > future = executorService.submit( callable );
futures.add( future );
}
System.out.println( "INFO - Submitted %d tasks.".formatted( limit ) );
}
finally
{
if ( Objects.nonNull( executorService ) ) { executorService.shutdown(); }
}
// Sleep our main thread long enough for background work to finish.
try
{
System.out.println( "INFO - Sleeping main thread." );
Thread.sleep( TimeUnit.MINUTES.toMillis( 1 ) );
}
catch ( InterruptedException e )
{
e.printStackTrace();
}
Instant done = Instant.now();
System.out.println( "INFO - Done running demo at " + done );
请注意,在Enrichment 类的这种特殊情况下,我不需要真正实例化新对象。我们可以简单地在 run 的所有 1000 万次执行中重复使用单个实例。但我想要一个更糟糕的例子——如果你的场景确实需要新的对象,我想看看对内存的大致影响。
在我在 64 位 Intel Mac mini 上使用来自 AdoptOpenJDK 的 Java 15 的试验中,这项工作花费了不到一分钟的时间,并使用 4.5 gigs 处理了 1000 万个任务。
顺便说一句,将来Project Loom 可能会简化您的工作。您将能够简单地安排数百万个“虚拟线程”(纤程)在有限数量的平台/内核线程上运行。 Project Loom 抢先体验Builds are available now。在 YouTube 上查看 Ron Pressler 2020 年末的演讲。