当遇到循环体时,try-catch该放在哪?

220 阅读2分钟

当遇到循环体时,try-catch该放在循环体的里面还是外面呢?哪种方式的性能更高呢?因此,本文便通过JMH来进行基准测试,到底哪种方式的性能更高些。

首先明白两个定义:

  1. JMH:是Java Microbenchmark Harness 的缩写。翻译成中文是“JAVA 微基准测试套件”的意思。
  2. 基准测试:百度百科的定义是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。

明白这两个定义之后,我们就进行测试了:

  • 在maven中导入:

     <dependencies>
            <!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->
            <dependency>
                <groupId>org.openjdk.jmh</groupId>
                <artifactId>jmh-core</artifactId>
                <version>1.25</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess -->
            <dependency>
                <groupId>org.openjdk.jmh</groupId>
                <artifactId>jmh-generator-annprocess</artifactId>
                <version>1.25</version>
                <scope>provided</scope>
            </dependency>
     </dependencies>
    

进行性能测试

在进行性能测试之前,介绍几个概念

  1. @BenchmarkMode:测试出平均耗时等信息。
  2. @OutputTimeUnit:基准测试结果时间的单位。
  3. @Warmup:用于配置预热参数。
  4. @Fork:指定fork出多少个子进程来执行同一基准测试方法。
  5. @State:用于声明某个类是一个状态。
  6. @Threads:指定使用多少个线程来执行基准测试方法。

以下是代码:

package com.hfut;

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.util.concurrent.TimeUnit;

/**
 * @author Chenzh
 */
@BenchmarkMode(Mode.AverageTime) /*每次调用平均耗时*/
@OutputTimeUnit(TimeUnit.SECONDS) /*基准测试结果时间的单位*/
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) /*预热1轮,每次预热结束等待时间为1,单位为s*/
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) /*迭代5次,执行完一次等待5s钟*/
@Fork(1) /*指定fork出多少个子进程来执行同一基准测试方法*/
@State(Scope.Benchmark) /*用于声明某个类是一个状态,Benchmark指该状态在所有线程间共享*/
@Threads(100) /*指定使用多少个线程来执行基准测试方法*/
public class Test {

    /**
     * 循环次数
     */
    private static final int LOOP_COUNT = 200;
    public static void main(String[] args) {
        Options ops = new OptionsBuilder().include(Test.class.getSimpleName()).build();
        // 执行测试
        try {
            new Runner(ops).run();
        } catch (RunnerException e) {
            e.printStackTrace();
        }
    }

    /**
     * try-catch在循环体里面
     * @return sum
     */
    @Benchmark
    public int innerLoop() {
        int sum = 0;
        for (int i = 0; i <= LOOP_COUNT; i++) {
            try {
                if (i == LOOP_COUNT) {
                    throw new Exception("出现异常");
                }
                sum += i;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return sum;
    }

    /**
     * try-catch在循环体外面
     * @return sum
     */
    @Benchmark
    public int outerLoop() {
        int sum = 0;
        try {
            for (int i = 0; i <= LOOP_COUNT; i++) {
                if (i == LOOP_COUNT) {
                    throw new Exception("出现异常");
                }
                sum += i;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sum;
    }
}

 代码解读:

预热一轮,每次预热结束后等待1s,共迭代5次,执行完一次迭代等待5s,共使用100个线程来执行该基准测试。

从结果中可以看出,当try-catch放在循环体里面耗时69.656-22.557ms ~ 69.656+22.557ms,try-catch放在循环体外面耗时69.034-20.748ms ~ 69.034+20.748ms,因此,这两种方式在性能上的表现相差无几。

结论:try-catch无论放在循环体里面还是外面,对性能的影响都差不多。