背景:
大學里學java,老師口口聲聲,言之鑿鑿,告誡我們,Java千萬別用異常控制業務流程,只有系統級別的問題,才能使用異常;
(當時,我們都不懂為什么不能用異常,只知道老師這么說,我們就這么做,考試才不會錯 :) )
公司里,有兩派.異常擁護者說,使用業務異常,代碼邏輯更清晰,更OOP;反之者說,使用異常,性能非常糟糕;
(當然,我是擁護者)
論壇上,爭論得更多,仁者見仁智者見智,口水很多;
(我不發表意見,其實怎么用,真的都可以)
那么,為什么反對異常呢?貌似大多數人的原因都只有一個:性能差!
使用異常性能真的差嗎? 是的!
是什么原因,導致性能問題呢? 那么請看下文...
根本原因在于:
異常基類Throwable.java的public synchronized native Throwable fillInStackTrace()方法
方法介紹:
Fills in the execution stack trace. This method records within this Throwable object information about the current state of the stack frames for the current thread.
性能開銷在于:
1. 是一個synchronized方法(主因)
2. 需要填充線程運行堆棧信息
但是對于業務異常來說,它只代表業務分支;壓根兒不需要stack信息的.如果去掉這個過程,是不是有性能提升呢?
于是做了一個簡單的測試對比,對比主體:
1。 創建普通Java對象 (CustomObject extends HashMap)
2。 創建普通Java異常對象 (CustomException extends Exception)
3。 創建改進的Java業務異常對象 (CustomException extends Exception,覆寫fillInStackTrace方法,并且去掉同步)
測試結果:
(運行環境:xen虛擬機,5.5G內存,8核;jdk1.6.0_18)
(10個線程,創建10000000個對象所需時間)
普通Java對象 45284 MS
普通java異常 205482 MS
改進的Java業務異常 16731 MS
測試代碼如下:
1 /**
2 * <pre>
3 * xen虛擬機,5.5G內存;8核CPU
4 * LOOP = 10000000
5 * THREADS = 10
6 * o: 45284
7 * e: 205482
8 * exte: 16731
9 * </pre>
10 *
11 * k
12 *
13 * @author li.jinl 2010-7-9 上午09:16:14
14 */
15 public class NewExceptionTester {
16
17 private static final int LOOP = 10000000; // 單次循環數量
18 private static final int THREADS = 10; // 并發線程數量
19
20 private static final List<Long> newObjectTimes = new ArrayList<Long>(THREADS);
21 private static final List<Long> newExceptionTimes = new ArrayList<Long>(THREADS);
22 private static final List<Long> newExtExceptionTimes = new ArrayList<Long>(THREADS);
23
24 private static final ExecutorService POOL = Executors.newFixedThreadPool(30);
25
26 public static void main(String[] args) throws Exception {
27 List<Callable<Boolean>> all = new ArrayList<Callable<Boolean>>();
28 all.addAll(tasks(new NewObject()));
29 all.addAll(tasks(new NewException()));
30 all.addAll(tasks(new NewExtException()));
31
32 POOL.invokeAll(all);
33
34 System.out.println("o:\t\t" + total(newObjectTimes));
35 System.out.println("e:\t\t" + total(newExceptionTimes));
36 System.out.println("exte:\t\t" + total(newExtExceptionTimes));
37
38 POOL.shutdown();
39 }
40
41 private static List<Callable<Boolean>> tasks(Callable<Boolean> c) {
42 List<Callable<Boolean>> list = new ArrayList<Callable<Boolean>>(THREADS);
43 for (int i = 0; i < THREADS; i++) {
44 list.add(c);
45 }
46 return list;
47 }
48
49 private static long total(List<Long> list) {
50 long sum = 0;
51 for (Long v : list) {
52 sum += v;
53 }
54 return sum;
55 }
56
57 public static class NewObject implements Callable<Boolean> {
58
59 @Override
60 public Boolean call() throws Exception {
61 long start = System.currentTimeMillis();
62 for (int i = 0; i < LOOP; i++) {
63 new CustomObject("");
64 }
65 newObjectTimes.add(System.currentTimeMillis() - start);
66 return true;
67 }
68
69 }
70
71 public static class NewException implements Callable<Boolean> {
72
73 @Override
74 public Boolean call() throws Exception {
75 long start = System.currentTimeMillis();
76 for (int i = 0; i < LOOP; i++) {
77 new CustomException("");
78 }
79 newExceptionTimes.add(System.currentTimeMillis() - start);
80 return true;
81 }
82
83 }
84
85 public static class NewExtException implements Callable<Boolean> {
86
87 @Override
88 public Boolean call() throws Exception {
89 long start = System.currentTimeMillis();
90 for (int i = 0; i < LOOP; i++) {
91 new ExtCustomException("");
92 }
93 newExtExceptionTimes.add(System.currentTimeMillis() - start);
94 return true;
95 }
96
97 }
98
99 /**
100 * 自定義java對象.
101 *
102 * @author li.jinl 2010-7-9 上午11:28:27
103 */
104 public static class CustomObject extends HashMap {
105
106 private static final long serialVersionUID = 5176739397156548105L;
107
108 private String message;
109
110 public CustomObject(String message){
111 this.message = message;
112 }
113
114 public String getMessage() {
115 return message;
116 }
117
118 public void setMessage(String message) {
119 this.message = message;
120 }
121
122 }
123
124 /**
125 * 自定義普通的Exception對象
126 *
127 * @author li.jinl 2010-7-9 上午11:28:58
128 */
129 public static class CustomException extends Exception {
130
131 private static final long serialVersionUID = -6879298763723247455L;
132
133 private String message;
134
135 public CustomException(String message){
136 this.message = message;
137 }
138
139 public String getMessage() {
140 return message;
141 }
142
143 public void setMessage(String message) {
144 this.message = message;
145 }
146
147 }
148
149 /**
150 * <pre>
151 * 自定義改進的Exception對象 覆寫了 fillInStackTrace方法
152 * 1. 不填充stack
153 * 2. 取消同步
154 * </pre>
155 *
156 * @author li.jinl 2010-7-9 上午11:29:12
157 */
158 public static class ExtCustomException extends Exception {
159
160 private static final long serialVersionUID = -6879298763723247455L;
161
162 private String message;
163
164 public ExtCustomException(String message){
165 this.message = message;
166 }
167
168 public String getMessage() {
169 return message;
170 }
171
172 public void setMessage(String message) {
173 this.message = message;
174 }
175
176 @Override
177 public Throwable fillInStackTrace() {
178 return this;
179 }
180 }
181 }
所以,如果我們業務異常的基類,一旦覆寫fillInStackTrace,并且去掉同步,那么異常性能有大幅度提升(因為業務異常本身也不需要堆棧信息)
如果說,創建異常的性能開銷大家已經有些感覺了,那么TryCatch是否也存在性能開銷呢?
接下來,做了一次try...catch 和 if...esle的性能比較
測試結果(運行環境和上面一樣):
20個線程,100000000,所消耗的時間:
try...catch: 101412MS
if...else: 100749MS
備注:
在我自己的開發機器上(xp和ubuntu下,單核),try...catch耗時是if...else的2倍(在同一數量級)
具體原因還未知,之后會使用專業的性能測試工具進行分析
測試代碼如下:
1 /**
2 * <pre>
3 * xen虛擬機,5.5G內存;8核CPU
4 * LOOP = 100000000
5 * THREADS = 20
6 *
7 * tc: 101412
8 * ie: 100749
9 * </pre>
10 *
11 * @author li.jinl 2010-7-9 上午10:47:56
12 */
13 public class ProcessTester {
14
15 private static final int LOOP = 100000000;
16 private static final int THREADS = 20;
17
18 private static final List<Long> tryCatchTimes = new ArrayList<Long>(THREADS);
19 private static final List<Long> ifElseTimes = new ArrayList<Long>(THREADS);
20
21 private static final ExecutorService POOL = Executors.newFixedThreadPool(40);
22
23 public static void main(String[] args) throws Exception {
24 List<Callable<Boolean>> all = new ArrayList<Callable<Boolean>>();
25 all.addAll(tasks(new TryCatch()));
26 all.addAll(tasks(new IfElse()));
27
28 POOL.invokeAll(all);
29
30 System.out.println("tc:\t\t" + total(tryCatchTimes));
31 System.out.println("ie:\t\t" + total(ifElseTimes));
32
33 POOL.shutdown();
34 }
35
36 private static List<Callable<Boolean>> tasks(Callable<Boolean> c) {
37 List<Callable<Boolean>> list = new ArrayList<Callable<Boolean>>(THREADS);
38 for (int i = 0; i < THREADS; i++) {
39 list.add(c);
40 }
41 return list;
42 }
43
44 private static long total(List<Long> list) {
45 long sum = 0;
46 for (Long v : list) {
47 sum += v;
48 }
49 return sum;
50 }
51
52 public static class TryCatch implements Callable<Boolean> {
53
54 @Override
55 public Boolean call() throws Exception {
56 long start = System.currentTimeMillis();
57 for (int i = 0; i < LOOP; i++) {
58 try {
59 exception();
60 // 
61 } catch (ExtCustomException e) {
62 // 
63 }
64 }
65 tryCatchTimes.add(System.currentTimeMillis() - start);
66 return true;
67 }
68
69 private void exception() throws ExtCustomException {
70 throw new ExtCustomException("");
71 }
72
73 }
74
75 public static class IfElse implements Callable<Boolean> {
76
77 @Override
78 public Boolean call() throws Exception {
79 long start = System.currentTimeMillis();
80 for (int i = 0; i < LOOP; i++) {
81 Exception e = exception();
82 if (e instanceof ExtCustomException) {
83 // 
84 }
85 }
86 ifElseTimes.add(System.currentTimeMillis() - start);
87 return true;
88 }
89
90 private Exception exception() {
91 return new ExtCustomException("");
92 }
93
94 }
95
96 public static class ExtCustomException extends Exception {
97
98 private static final long serialVersionUID = -6879298763723247455L;
99
100 private String message;
101
102 public ExtCustomException(String message){
103 this.message = message;
104 }
105
106 public String getMessage() {
107 return message;
108 }
109
110 public void setMessage(String message) {
111 this.message = message;
112 }
113
114 @Override
115 public Throwable fillInStackTrace() {
116 return this;
117 }
118
119 }
120
121 }

/**
* <pre>
* xen虛擬機,5.5G內存;8核CPU
* LOOP = 10000000
* THREADS = 10
* o: 45284
* e: 205482
* exte: 16731
* </pre>
*
* k
*
* @author li.jinl 2010-7-9 上午09:16:14
*/
public class NewExceptionTester {
private static final int LOOP = 10000000; // 單次循環數量
private static final int THREADS = 10; // 并發線程數量
private static final List<Long> newObjectTimes = new ArrayList<Long>(THREADS);
private static final List<Long> newExceptionTimes = new ArrayList<Long>(THREADS);
private static final List<Long> newExtExceptionTimes = new ArrayList<Long>(THREADS);
private static final ExecutorService POOL = Executors.newFixedThreadPool(30);
public static void main(String[] args) throws Exception {
List<Callable<Boolean>> all = new ArrayList<Callable<Boolean>>();
all.addAll(tasks(new NewObject()));
all.addAll(tasks(new NewException()));
all.addAll(tasks(new NewExtException()));
POOL.invokeAll(all);
System.out.println("o:\t\t" + total(newObjectTimes));
System.out.println("e:\t\t" + total(newExceptionTimes));
System.out.println("exte:\t\t" + total(newExtExceptionTimes));
POOL.shutdown();
}
private static List<Callable<Boolean>> tasks(Callable<Boolean> c) {
List<Callable<Boolean>> list = new ArrayList<Callable<Boolean>>(THREADS);
for (int i = 0; i < THREADS; i++) {
list.add(c);
}
return list;
}
private static long total(List<Long> list) {
long sum = 0;
for (Long v : list) {
sum += v;
}
return sum;
}
public static class NewObject implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
long start = System.currentTimeMillis();
for (int i = 0; i < LOOP; i++) {
new CustomObject("");
}
newObjectTimes.add(System.currentTimeMillis() - start);
return true;
}
}
public static class NewException implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
long start = System.currentTimeMillis();
for (int i = 0; i < LOOP; i++) {
new CustomException("");
}
newExceptionTimes.add(System.currentTimeMillis() - start);
return true;
}
}
public static class NewExtException implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
long start = System.currentTimeMillis();
for (int i = 0; i < LOOP; i++) {
new ExtCustomException("");
}
newExtExceptionTimes.add(System.currentTimeMillis() - start);
return true;
}
}
/**
* 自定義java對象.
*
* @author li.jinl 2010-7-9 上午11:28:27
*/
public static class CustomObject extends HashMap {
private static final long serialVersionUID = 5176739397156548105L;
private String message;
public CustomObject(String message){
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
/**
* 自定義普通的Exception對象
*
* @author li.jinl 2010-7-9 上午11:28:58
*/
public static class CustomException extends Exception {
private static final long serialVersionUID = -6879298763723247455L;
private String message;
public CustomException(String message){
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
/**
* <pre>
* 自定義改進的Exception對象 覆寫了 fillInStackTrace方法
* 1. 不填充stack
* 2. 取消同步
* </pre>
*
* @author li.jinl 2010-7-9 上午11:29:12
*/
public static class ExtCustomException extends Exception {
private static final long serialVersionUID = -6879298763723247455L;
private String message;
public ExtCustomException(String message){
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public Throwable fillInStackTrace() {
return this;
}
}
結論:
1。Exception的性能是差,原因在于ThrowablefillInStackTrace()方法
2. 可以通過改寫業務異常基類的方法,提升性能
3。try...catch和if...else的性能開銷在同一數量級
4。至于是否使用異常進行業務邏輯的控制,主要看代碼風格.(我個人挺喜歡業務異常的)
備注:
以上測試比較簡單,寫得也比較急.此文也寫得比較急(工作時間偷偷寫).如果分析不到位的地方,請指出.