啪啪敦跌,打臉了!領導說:try-catch必須放在循環(huán)體外逛揩!

今天給大家?guī)淼氖顷P于try-catch 應該放在循環(huán)體外柠傍,還是放在循環(huán)體內(nèi)的文章,我們將從性能業(yè)務場景分析這兩個方面來回答此問題息尺。

很多人對 try-catch 有一定的誤解携兵,比如我們經(jīng)常會把它(try-catch)和“低性能”直接畫上等號,但對 try-catch 的本質(zhì)(是什么)卻缺少著最基礎的了解搂誉,因此我們也會在本篇中對 try-catch 的本質(zhì)進行相關的探索徐紧。

性能評測

話不多說,我們直接來開始今天的測試,本文我們依舊使用 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; // 循環(huán)次數(shù)

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

? ? ? ? // 啟動基準測試

? ? ? ? Options opt = new OptionsBuilder()

? ? ? ? ? ? ? ? .include(TryCatchPerformanceTest.class.getSimpleName()) // 要導入的測試類

? ? ? ? ? ? ? ? .build();

? ? ? ? new Runner(opt).run(); // 執(zhí)行測試

? ? }

? ? @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;

? ? }

}


以上代碼的測試結(jié)果為:

從以上結(jié)果可以看出,程序在循環(huán) 1000 次的情況下愈涩,單次平均執(zhí)行時間為:循環(huán)內(nèi)包含 try-catch 的平均執(zhí)行時間是 635 納秒 ±75 納秒望抽,也就是 635 納秒上下誤差是 75 納秒;

循環(huán)外包含 try-catch 的平均執(zhí)行時間是 630 納秒履婉,上下誤差 38 納秒煤篙。

也就是說,在沒有發(fā)生異常的情況下毁腿,除去誤差值辑奈,我們得到的結(jié)論是:try-catch 無論是在for循環(huán)內(nèi)還是for循環(huán)外,它們的性能相同已烤,幾乎沒有任何差別鸠窗。

try-catch的本質(zhì)

要理解 try-catch 的性能問題,必須從它的字節(jié)碼開始分析胯究,只有這樣我能才能知道 try-catch 的本質(zhì)到底是什么稍计,以及它是如何執(zhí)行的。

此時我們寫一個最簡單的 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生成字節(jié)碼之后唐片,再使用javap -c AppTest的命令來查看字節(jié)碼文件:

? 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

}

從以上字節(jié)碼中可以看到有一個異常表:

Exception table:

? ? ? from? ? to? target type

? ? ? ? ? 0? ? 12? ? 12? Class java/lang/Exception

參數(shù)說明:

from:表示 try-catch 的開始地址丙猬;

to:表示 try-catch 的結(jié)束地址;

target:表示異常的處理起始位费韭;

type:表示異常類名稱。

從字節(jié)碼指令可以看出庭瑰,當代碼運行時出錯時星持,會先判斷出錯數(shù)據(jù)是否在from到to的范圍內(nèi),如果是則從target標志位往下執(zhí)行弹灭,如果沒有出錯督暂,直接goto到return。也就是說穷吮,如果代碼不出錯的話逻翁,性能幾乎是不受影響的,和正常的代碼的執(zhí)行邏輯是一樣的捡鱼。

業(yè)務情況分析

雖然 try-catch 在循環(huán)體內(nèi)還是循環(huán)體外的性能是類似的八回,但是它們所代碼的業(yè)務含義卻完全不同,例如以下代碼:

public class AppTest {

? ? public static void main(String[] args) {

? ? ? ? System.out.println("循環(huán)內(nèi)的執(zhí)行結(jié)果:" + innerForeach());

? ? ? ? System.out.println("循環(huán)外的執(zhí)行結(jié)果:" + 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;

? ? }

}

以上程序的執(zhí)行結(jié)果為:

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)

循環(huán)內(nèi)的執(zhí)行結(jié)果:5

循環(huán)外的執(zhí)行結(jié)果:3

可以看出在循環(huán)體內(nèi)的 try-catch 在發(fā)生異常之后,可以繼續(xù)執(zhí)行循環(huán)缠诅;而循環(huán)外的 try-catch 在發(fā)生異常之后會終止循環(huán)溶浴。

因此我們在決定 try-catch 究竟是應該放在循環(huán)內(nèi)還是循環(huán)外,不取決于性能(因為性能幾乎相同)管引,而是應該取決于具體的業(yè)務場景士败。

例如我們需要處理一批數(shù)據(jù),而無論這組數(shù)據(jù)中有哪一個數(shù)據(jù)有問題褥伴,都不能影響其他組的正常執(zhí)行谅将,此時我們可以把 try-catch 放置在循環(huán)體內(nèi);而當我們需要計算一組數(shù)據(jù)的合計值時重慢,只要有一組數(shù)據(jù)有誤饥臂,我們就需要終止執(zhí)行,并拋出異常伤锚,此時我們需要將 try-catch 放置在循環(huán)體外來執(zhí)行擅笔。

總結(jié)

本文我們測試了 try-catch 放在循環(huán)體內(nèi)和循環(huán)體外的性能,發(fā)現(xiàn)二者在循環(huán)很多次的情況下性能幾乎是一致的屯援。然后我們通過字節(jié)碼分析猛们,發(fā)現(xiàn)只有當發(fā)生異常時,才會對比異常表進行異常處理狞洋,而正常情況下則可以忽略 try-catch 的執(zhí)行弯淘。但在循環(huán)體內(nèi)還是循環(huán)體外使用 try-catch,對于程序的執(zhí)行結(jié)果來說是完全不同的吉懊,因此我們應該從實際的業(yè)務出發(fā)庐橙,來決定到 try-catch 應該存放的位置,而非性能考慮借嗽。

作者:Java中文社群

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

來源:掘金

著作權(quán)歸作者所有态鳖。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處恶导。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末浆竭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子惨寿,更是在濱河造成了極大的恐慌邦泄,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裂垦,死亡現(xiàn)場離奇詭異顺囊,居然都是意外死亡,警方通過查閱死者的電腦和手機蕉拢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門特碳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诚亚,“玉大人,你說我怎么就攤上這事测萎⊥龅纾” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵硅瞧,是天一觀的道長份乒。 經(jīng)常有香客問我,道長腕唧,這世上最難降的妖魔是什么或辖? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮枣接,結(jié)果婚禮上颂暇,老公的妹妹穿的比我還像新娘。我一直安慰自己但惶,他們只是感情好耳鸯,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著膀曾,像睡著了一般县爬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上添谊,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天财喳,我揣著相機與錄音,去河邊找鬼斩狱。 笑死耳高,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的所踊。 我是一名探鬼主播泌枪,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼秕岛!你這毒婦竟也來了工闺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤瓣蛀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后雷厂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惋增,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年改鲫,在試婚紗的時候發(fā)現(xiàn)自己被綠了诈皿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片林束。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖稽亏,靈堂內(nèi)的尸體忽然破棺而出壶冒,到底是詐尸還是另有隱情,我是刑警寧澤截歉,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布胖腾,位于F島的核電站,受9級特大地震影響瘪松,放射性物質(zhì)發(fā)生泄漏咸作。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一宵睦、第九天 我趴在偏房一處隱蔽的房頂上張望记罚。 院中可真熱鬧,春花似錦壳嚎、人聲如沸桐智。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽说庭。三九已至,卻和暖如春焙糟,著一層夾襖步出監(jiān)牢的瞬間口渔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工穿撮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留耸弄,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓绢馍,卻偏偏與公主長得像胖齐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子栗柒,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348