如何用JMH基准测试Java方法中特定的future.get()循环代码段
用JMH测试future.get()循环的可行方案
你这个需求是可以实现的,核心是借助JMH的状态管理和生命周期注解,把前置准备逻辑和要测试的future.get()循环拆分,让准备工作在基准测试启动前完成,同时规避JMH进程隔离的限制。
具体实现思路
- 用
@State(Scope.Benchmark)定义状态类,存放ScheduledExecutorService、Collection<Future>、测试文件等资源,JMH会在基准测试进程中自动管理这个状态实例。 - 用
@Setup(Level.Trial)标记初始化方法,在整个基准测试批次启动前仅执行一次:创建测试文件、提交所有异步任务并收集Future对象。 - 基准测试方法只保留
future.get()的循环逻辑,直接从状态类中获取预先生成的futures集合。 - 用
@TearDown(Level.Trial)标记清理方法,测试结束后关闭线程池、删除测试文件,避免资源残留。
完整代码示例
import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.Collection; import java.util.HashSet; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; @State(Scope.Benchmark) public class FutureGetBenchmark { private ScheduledExecutorService exec; private Collection<Future<?>> futures; private File testFile; @Setup(Level.Trial) public void setup() throws IOException { // 初始化线程池 exec = new ScheduledThreadPoolExecutor(50); testFile = new File("./test"); testFile.createNewFile(); futures = new HashSet<>(); // 提交所有异步任务并收集Future for (int i = 0; i < 50; i++) { final int j = i; Future<?> future = exec.submit(() -> { try { Files.write(testFile.toPath(), ("Test-" + j).getBytes(StandardCharsets.UTF_8)); } catch (IOException e) { throw new RuntimeException(e); } }); futures.add(future); } } @Benchmark @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) public void benchmarkFutureGet() throws Exception { // 仅测试这段future.get()循环逻辑 for (Future<?> future : futures) { future.get(); } } @TearDown(Level.Trial) public void tearDown() throws IOException { // 清理资源 exec.shutdown(); if (testFile.exists()) { testFile.delete(); } } public static void main(String[] args) throws RunnerException { Options options = new OptionsBuilder() .include(FutureGetBenchmark.class.getSimpleName()) .forks(1) .warmupIterations(3) .measurementIterations(5) .build(); new Runner(options).run(); } }
关键注意事项
@Setup(Level.Trial)保证初始化逻辑只执行一次,避免每次基准测试迭代都重复提交任务,确保测试的是纯粹等待Future完成的耗时。- 虽然方案可行,但文件IO的不确定性(磁盘性能、系统缓存等)会干扰测试结果的稳定性。如果只是替代
System.currentTimeMillis测量等待耗时,建议把异步任务中的IO换成无副作用的计算(比如简单数值运算),这样结果更能反映future.get()本身的性能。 - JMH默认会fork新进程执行测试,状态类实例会在fork后的进程中初始化,完全适配JMH的运行机制。
内容的提问来源于stack exchange,提问作者PentaKon
相关产品推荐
相关产品推荐

