美文网首页
啪啪,打脸了!领导说:try-catch必须放在循环体外!

啪啪,打脸了!领导说:try-catch必须放在循环体外!

作者: e4fb6e2425b9 | 来源:发表于2020-06-08 17:20 被阅读0次

    今天给大家带来的是关于try-catch 应该放在循环体外,还是放在循环体内的文章,我们将从性能业务场景分析这两个方面来回答此问题。

    很多人对 try-catch 有一定的误解,比如我们经常会把它(try-catch)和“低性能”直接画上等号,但对 try-catch 的本质(是什么)却缺少着最基础的了解,因此我们也会在本篇中对 try-catch 的本质进行相关的探索

    性能评测

    话不多说,我们直接来开始今天的测试,本文我们依旧使用 Oracle 官方提供的 JMH(Java Microbenchmark Harness,JAVA 微基准测试套件)来进行测试。

    首先在 pom.xml 文件中添加 JMH 框架,配置如下:

     <!-- https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core -->

    <dependency>

      <groupId>org.openjdk.jmh</groupId>

      <artifactId>jmh-core</artifactId>

      <version>{version}</version>

    </dependency>

    完整测试代码如下:

    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;

    /**

    * try - catch 性能测试

    */

    @BenchmarkMode(Mode.AverageTime) // 测试完成时间

    @OutputTimeUnit(TimeUnit.NANOSECONDS)

    @Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 1 轮,每次 1s

    @Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s

    @Fork(1) // fork 1 个线程

    @State(Scope.Benchmark)

    @Threads(100)

    public class TryCatchPerformanceTest {

        private static final int forSize = 1000; // 循环次数

        public static void main(String[] args) throws RunnerException {

            // 启动基准测试

            Options opt = new OptionsBuilder()

                    .include(TryCatchPerformanceTest.class.getSimpleName()) // 要导入的测试类

                    .build();

            new Runner(opt).run(); // 执行测试

        }

        @Benchmark

        public int innerForeach() {

            int count = 0;

            for (int i = 0; i < forSize; i++) {

                try {

                    if (i == forSize) {

                        throw new Exception("new Exception");

                    }

                    count++;

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

            return count;

        }

        @Benchmark

        public int outerForeach() {

            int count = 0;

            try {

                for (int i = 0; i < forSize; i++) {

                    if (i == forSize) {

                        throw new Exception("new Exception");

                    }

                    count++;

                }

            } catch (Exception e) {

                e.printStackTrace();

            }

            return count;

        }

    }

    以上代码的测试结果为:

    从以上结果可以看出,程序在循环 1000 次的情况下,单次平均执行时间为:循环内包含 try-catch 的平均执行时间是 635 纳秒 ±75 纳秒,也就是 635 纳秒上下误差是 75 纳秒;

    循环外包含 try-catch 的平均执行时间是 630 纳秒,上下误差 38 纳秒。

    也就是说,在没有发生异常的情况下,除去误差值,我们得到的结论是:try-catch 无论是在for循环内还是for循环外,它们的性能相同,几乎没有任何差别

    try-catch的本质

    要理解 try-catch 的性能问题,必须从它的字节码开始分析,只有这样我能才能知道 try-catch 的本质到底是什么,以及它是如何执行的。

    此时我们写一个最简单的 try-catch 代码:

    public class AppTest {

        public static void main(String[] args) {

            try {

                int count = 0;

                throw new Exception("new Exception");

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

    }

    然后使用javac生成字节码之后,再使用javap -c AppTest的命令来查看字节码文件:

    ➜ javap -c AppTest

    警告: 二进制文件AppTest包含com.example.AppTest

    Compiled from "AppTest.java"

    public class com.example.AppTest {

      public com.example.AppTest();

        Code:

          0: aload_0

          1: invokespecial #1                  // Method java/lang/Object."<init>":()V

          4: return

      public static void main(java.lang.String[]);

        Code:

          0: iconst_0

          1: istore_1

          2: new          #2                  // class java/lang/Exception

          5: dup

          6: ldc          #3                  // String new Exception

          8: invokespecial #4                  // Method java/lang/Exception."<init>":(Ljava/lang/String;)V

          11: athrow

          12: astore_1

          13: aload_1

          14: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V

          17: return

        Exception table:

          from    to  target type

              0    12    12  Class java/lang/Exception

    }

    从以上字节码中可以看到有一个异常表:

    Exception table:

          from    to  target type

              0    12    12  Class java/lang/Exception

    参数说明:

    from:表示 try-catch 的开始地址;

    to:表示 try-catch 的结束地址;

    target:表示异常的处理起始位;

    type:表示异常类名称。

    从字节码指令可以看出,当代码运行时出错时,会先判断出错数据是否在from到to的范围内,如果是则从target标志位往下执行,如果没有出错,直接goto到return。也就是说,如果代码不出错的话,性能几乎是不受影响的,和正常的代码的执行逻辑是一样的。

    业务情况分析

    虽然 try-catch 在循环体内还是循环体外的性能是类似的,但是它们所代码的业务含义却完全不同,例如以下代码:

    public class AppTest {

        public static void main(String[] args) {

            System.out.println("循环内的执行结果:" + innerForeach());

            System.out.println("循环外的执行结果:" + outerForeach());

        }

        // 方法一

        public static int innerForeach() {

            int count = 0;

            for (int i = 0; i < 6; i++) {

                try {

                    if (i == 3) {

                        throw new Exception("new Exception");

                    }

                    count++;

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

            return count;

        }

        // 方法二

        public static int outerForeach() {

            int count = 0;

            try {

                for (int i = 0; i < 6; i++) {

                    if (i == 3) {

                        throw new Exception("new Exception");

                    }

                    count++;

                }

            } catch (Exception e) {

                e.printStackTrace();

            }

            return count;

        }

    }

    以上程序的执行结果为:

    java.lang.Exception: new Exception

    at com.example.AppTest.innerForeach(AppTest.java:15)

    at com.example.AppTest.main(AppTest.java:5)

    java.lang.Exception: new Exception

    at com.example.AppTest.outerForeach(AppTest.java:31)

    at com.example.AppTest.main(AppTest.java:6)

    循环内的执行结果:5

    循环外的执行结果:3

    可以看出在循环体内的 try-catch 在发生异常之后,可以继续执行循环;而循环外的 try-catch 在发生异常之后会终止循环。

    因此我们在决定 try-catch 究竟是应该放在循环内还是循环外,不取决于性能(因为性能几乎相同),而是应该取决于具体的业务场景

    例如我们需要处理一批数据,而无论这组数据中有哪一个数据有问题,都不能影响其他组的正常执行,此时我们可以把 try-catch 放置在循环体内;而当我们需要计算一组数据的合计值时,只要有一组数据有误,我们就需要终止执行,并抛出异常,此时我们需要将 try-catch 放置在循环体外来执行。

    总结

    本文我们测试了 try-catch 放在循环体内和循环体外的性能,发现二者在循环很多次的情况下性能几乎是一致的。然后我们通过字节码分析,发现只有当发生异常时,才会对比异常表进行异常处理,而正常情况下则可以忽略 try-catch 的执行。但在循环体内还是循环体外使用 try-catch,对于程序的执行结果来说是完全不同的,因此我们应该从实际的业务出发,来决定到 try-catch 应该存放的位置,而非性能考虑

    作者:Java中文社群

    链接:https://juejin.im/post/5ed5b998f265da76bd1ad012

    来源:掘金

    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

          本文标题:啪啪,打脸了!领导说:try-catch必须放在循环体外!

          本文链接:https://www.haomeiwen.com/subject/svrrtktx.html