在application server下,比如常见的weblogic,glassfish,jboss等,由于javaee规范的要求,一般不容许直接启动线程。因此在常见的异步/并行任务执行上,会遭遇到比普通javase程序更多的麻烦。
典型例子,在javase中,jdk1.5后就引入了java.util.concurrent包,提供Executor这个非常好用的框架,完美的满足一下典型需求:
1. 同步变异步
请求进来后,将请求封装为task,交给Executor执行,原线程可以立即返回
2. 并行执行
请求进来后,将请求拆分为若干个task,例如下发短信,有100个收件人就可以按照每个收件人一个task来执行,这样可以通过Executor来并行执行这些请求,远比循环执行要快的多。
3. 等待任务结束
有时有要求调用线程必须等待所有任务完成后再继续运行的需要,此外还有超时等细节设置要求。
而在application server,为了避开自己启动线程的弊端,只好通过其他的方式来完成类似的功能。
目前我们的项目开发中主要有三种实现方式:
1. jms queue
通过jms来实现异步和并发,然后自己通过编码方式完成调用线程等待所有任务执行成功。
这个方案比较通用,因为jms是javaee的标准,所有的application server上都支持。因此天然具有跨application server的能力。
缺点就比较多了,首先jms是需要实现串行化的,因此对task是有要求,不能串行化的类是不能传递的。另外串行化的性能损失比较大,造成性能和稳定性问题,这个在大压力下比较突出,基本我们目前在考虑放弃这个方案,而且逐步将原有的实现替换掉。
这个方案还有另外一个缺点,配置麻烦,维护困难:需要创建jsm queque, connection factory, MDB等,如果系统中使用的多了,配置起来很罗嗦,修改时容许出错。
2. commonj work manager
这个是weblogic和WebSphere上支持的一个很实用的解决方案,个人感觉使用上非常舒服,配置简单,只要在weblogic-ejb-jar.xml中间中简单配置:
<work-manager>
<name>wm/taskDistributionWorkManager</name>
<min-threads-constraint>
<name>minthreads</name>
<count>1</count>
</min-threads-constraint>
<max-threads-constraint>
<name>maxthreads</name>
<count>100</count>
</max-threads-constraint>
</work-manager>
使用时用jdni lookup到就可以使用了。功能和使用方式和executor框架很类似,同样提供future,而且提供一个非常实用的waitAll()方法论支持等待任务完成。
这个方案的性能非常好,和jms相比提升极大,运行也稳定。缺点就是不是标准,只有weblogic和WebSphere执行,在glassfish,jboss上无法使用。
3. JCA work manager
这个是JCA标准了,glassfish,jboss都支持的,和commonj work manager很像,但是,很遗憾的是没有future的支持,而且也没有类似的waitAll()方法,只能自己编码实现。
spring为glassfish提供了一个工具类"org.springframework.jca.work.WorkManagerTaskExecutor",简化了JCA work manager的使用。
JCA work manager的性能和稳定性都还不错,对比jms要好的多。
从目前我们项目的使用经验上看,jms是准备要被淘汰的了(其实我是一直对jms不感冒的,尤其是在有性能要求的地方,想不出用jms的理由)。目前项目要求同时支持weblogic和glassfish,因此commonj work manager和JCA work manager刚好对应于weblogic和glassfish平台。实际使用中,是在这两个work manager上封装了一个通用的接口,然后再有commonj work manager和JCA work manager两个实现,在运行时通过判断平台来自动选择注入其中的一个。