背景:
大学里学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。至于是否使用异常进行业务逻辑的控制,主要看代码风格.(我个人挺喜欢业务异常的)
备注:
以上测试比较简单,写得也比较急.此文也写得比较急(工作时间偷偷写).如果分析不到位的地方,请指出.