前言
我們經(jīng)常在很多項(xiàng)目里面看到用異常來處理業(yè)務(wù)邏輯,發(fā)現(xiàn)不符合預(yù)期直接拋出異常,然后在最外面捕獲異常統(tǒng)一處理,這樣使用非常方便。
但是又有很多文章寫著異常處理性能,所以不建議使用異常來做流程控制。甚至在阿里巴巴開發(fā)手冊(cè)里面明確說明了,不要用來做流程控制。

那么問題來了:
究竟能不能用異常來做流程控制?效率低是低多少?看完這一篇文章你就知道了。
開始測(cè)試
先做最簡(jiǎn)單的測(cè)試
我們循環(huán)10萬次,然后棧有5層,然后輸出返回結(jié)果。
private static final int RUN_COUNT = 10 * 10000;
/**
* 測(cè)試異常耗時(shí)
* 輸出異常堆棧&信息
*/
@Test
public void printStack() {
long start1 = System.currentTimeMillis();
for (int i = 0; i < RUN_COUNT; i++) {
log.info(Storey1.test());
}
long start2 = System.currentTimeMillis();
for (int i = 0; i < RUN_COUNT; i++) {
try {
Storey1.testException();
} catch (Exception e) {
log.info(e.getMessage(), e);
}
}
long end = System.currentTimeMillis();
log.info("普通返回耗時(shí):{},異常返回耗時(shí):{}", start2 - start1, end - start1);
}
public static class Storey1 {
public static String test() {
return Storey2.test();
}
public static String testException() {
return Storey2.testException();
}
}
public static class Storey2 {
public static String test() {
return Storey3.test();
}
public static String testException() {
return Storey3.testException();
}
}
public static class Storey3 {
public static String test() {
return Storey4.test();
}
public static String testException() {
return Storey4.testException();
}
}
public static class Storey4 {
public static String test() {
return Storey5.test();
}
public static String testException() {
return Storey5.testException();
}
}
public static class Storey5 {
public static String test() {
return Integer.toString(count++);
}
public static String testException() {
throw new CustomException(Integer.toString(count++));
}
}
public static class CustomException extends RuntimeException {
public CustomException(String message) {
super(message);
}
}
結(jié)果差別很大,普通返回只要2137毫秒,而異常卻要75026毫秒,幾十倍的差距。
15:07:59.648 [main] INFO com.alibaba.easytools.test.temp.exception.ExceptionTest - 普通返回耗時(shí):2137,異常返回耗時(shí):75026
不輸出堆棧信息
聰明的同學(xué)不難發(fā)現(xiàn),上面有個(gè)變量沒控制住,就是使用異常的情況下,輸出了堆棧信息,那我們關(guān)閉堆棧輸出試試。會(huì)不會(huì)是輸出的堆棧信息導(dǎo)致的慢呢?
/**
* 測(cè)試異常耗時(shí)
* 僅僅輸出信息
*/
@Test
public void print() {
long start1 = System.currentTimeMillis();
for (int i = 0; i < RUN_COUNT; i++) {
log.info(Storey1.test());
}
long start2 = System.currentTimeMillis();
for (int i = 0; i < RUN_COUNT; i++) {
try {
Storey1.testException();
} catch (Exception e) {
log.info(e.getMessage());
}
}
long end = System.currentTimeMillis();
log.info("普通返回耗時(shí):{},異常返回耗時(shí):{}", start2 - start1, end - start1);
}
結(jié)果發(fā)現(xiàn)普通返回是2053毫秒,而異常卻要4380毫秒,發(fā)現(xiàn)差距瞬間變小了。
15:43:54.260 [main] INFO com.alibaba.easytools.test.temp.exception.ExceptionTest - 普通返回耗時(shí):2053,異常返回耗時(shí):4380
不輸出任何信息
顯然我們發(fā)現(xiàn),關(guān)閉了日志輸出對(duì)執(zhí)行時(shí)間影像很大,那我們關(guān)閉了日志輸出會(huì)有什么效果了呢?
/**
* 測(cè)試異常耗時(shí)
* 不輸出信息
*/
@Test
public void noPrint() {
long start1 = System.currentTimeMillis();
for (int i = 0; i < RUN_COUNT; i++) {
Storey1.test();
}
long start2 = System.currentTimeMillis();
for (int i = 0; i < RUN_COUNT; i++) {
try {
Storey1.testException();
} catch (Exception e) {
}
}
long end = System.currentTimeMillis();
log.info("普通返回耗時(shí):{},異常返回耗時(shí):{}", start2 - start1, end - start1);
}
結(jié)果發(fā)現(xiàn)普通返回是58毫秒,而異常卻要719毫秒,看來性能實(shí)際差距就是十幾倍。
15:47:55.901 [main] INFO com.alibaba.easytools.test.temp.exception.ExceptionTest - 普通返回耗時(shí):58,異常返回耗時(shí):719
關(guān)閉堆棧
在處理異常的時(shí)候,很多時(shí)間在封裝異常堆棧,那有沒有辦法可以不要封裝呢?
仔細(xì)研究異常類發(fā)現(xiàn),異常類有個(gè)參數(shù)`writableStackTrace` 可以讓異常不去封裝堆棧信息。
public class RuntimeException extends Exception {
/**
* Constructs a new runtime exception with the specified detail
* message, cause, suppression enabled or disabled, and writable
* stack trace enabled or disabled.
*
* @param message the detail message.
* @param cause the cause. (A {@code null} value is permitted,
* and indicates that the cause is nonexistent or unknown.)
* @param enableSuppression whether or not suppression is enabled
* or disabled
* @param writableStackTrace whether or not the stack trace should
* be writable
*
* @since 1.7
*/
protected RuntimeException(String message, Throwable cause,
boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
我們把拋異常的時(shí)候不去封裝異常信息
/**
* 測(cè)試異常耗時(shí)
* 關(guān)閉堆棧 并且不打印
*/
@Test
public void noPrintCloseStackTrace() {
long start1 = System.currentTimeMillis();
for (int i = 0; i < RUN_COUNT; i++) {
Storey1.test();
}
long start2 = System.currentTimeMillis();
for (int i = 0; i < RUN_COUNT; i++) {
try {
Storey1.testException();
} catch (Exception e) {
}
}
long end = System.currentTimeMillis();
log.info("普通返回耗時(shí):{},異常返回耗時(shí):{}", start2 - start1, end - start1);
}
public static class Storey5 {
public static String test() {
return Integer.toString(count++);
}
public static String testException() { throw new CustomException(Integer.toString(count++), null, false, false);
}
}
public static class CustomException extends RuntimeException {
public CustomException(String message) {
super(message);
}
public CustomException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
結(jié)果發(fā)現(xiàn)普通返回是31毫秒,而異常卻要62毫秒,差距也沒有想象中的大了。差不多是2倍左右。
15:54:26.984 [main] INFO com.alibaba.easytools.test.temp.exception.ExceptionTest - 普通返回耗時(shí):31,異常返回耗時(shí):62
最終結(jié)果
我們來看下最終對(duì)比結(jié)論
|
普通 |
異常 |
普通輸出日志,異常輸出堆棧 |
2137 |
75026 |
普通輸出日志,異常僅輸出日志 |
2053 |
4380 |
都不輸出日志 |
58 |
719 |
關(guān)閉堆棧 |
31 |
62 |
結(jié)論
所以我們可以總結(jié)出以下結(jié)論
- 日志輸出堆棧非常耗時(shí)
- 哪怕日志只是輸出業(yè)務(wù)邏輯,耗時(shí)和業(yè)務(wù)處理也不是一個(gè)時(shí)間維度的
- 排出日志影響,封裝堆棧非常耗時(shí)
- 關(guān)閉堆棧以后耗時(shí)相差不大,大概1萬次相差3毫秒
三種方式優(yōu)缺點(diǎn)總結(jié)下:
|
優(yōu)點(diǎn) |
缺點(diǎn) |
普通 |
|
|
關(guān)閉堆棧的異常 |
|
|
不關(guān)閉堆棧的異常 |
|
|
如果我們的并發(fā)沒有大到必須關(guān)閉日志這種情況下,實(shí)際上來說異常來控制流程問題不大,影響微乎其微,所以還是怎么方便怎么來。
當(dāng)然如果項(xiàng)目并發(fā)超級(jí)高,高到單機(jī)1萬次請(qǐng)求要省3毫秒的情況下,建議還是用返回去控制業(yè)務(wù)流程。