背景:
大学里学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 }

结论:
1。Exception的性能是差,原因在于ThrowablefillInStackTrace()方法
2. 可以通过改写业务异常基类的方法,提升性能
3。try...catch和if...else的性能开销在同一数量级

4。至于是否使用异常进行业务逻辑的控制,主要看代码风格.(我个人挺喜欢业务异常的)

备注:
以上测试比较简单,写得也比较急.此文也写得比较急(工作时间偷偷写).如果分析不到位的地方,请指出.