JavaEE 6 中的EJB单例Singleton

单例Singleton和Prototype模式(Pool多例池化)曾经是两个截然相反的模式,EJB的无态会话Bean最早是以Pool形式出现,后来颠覆EJB的Spring提出Singleton,所有业务类缺省都是单例的,当时曾经引起本人的大跌眼镜,也发过抱怨,单例中状态访问需要注意锁模式。

TSS网站曾经有人提出Object Pool是性能杀手,引起轩然大波,参与讨论人数之最大概算TSS有史以来最长帖子,在Java并发编程一书中,作者认为大的类可以使用Pool,小类使用Pool反而会降低性能,本人亲自动手,对Jdon框架6.0中一个案例小应用分别在单例和Pool下进行测试,这个类代码不大,属于小类,发现两者性能区别不大,都是大约70毫秒左右。

后来有人写了Object Pooling - Determinism vs. Throughput(宿命论 vs. 吞吐量),提出Performance does not always mean Throughput,性能并不总是意味吞吐量,Pool通过对象池最大个数来限制Throughput吞吐量,但是限制吞吐量是为了保证现有通过的请求响应能够快速完成,如果没有最大个数限制,不断增加的请求会拖慢每个请求的响应时间。作者提出几种情况下使用Pool:
1.GC微调不能帮助你
2.应用创建大量对象,你的对象创建是很费时,但是易于循环使用。
3.宿命论,响应时间相对对于吞吐量非常重要。

http://michael-bien.com/mbien/entry/object_pooling_determinism_vs_throughput


前面谈了POOL模式使用场景,其实也是EJB无态Bean的使用场景,因为它缺省是POOL的,无论EJB1/EJB2/EJB3以及即将到来的JavaEE 6.0中的EJB3.1,都是这样。

不过,EJB拒绝单例的先例从JavaEE 6开始打破,引入了单例注解,这样,对于应用就有些选择,但是这个单例还是逃不了分布式环境中没有绝对单例的前提,只有每一个JVM是单例,但是EJB3.1没有提及如何保证在整个集群环境下支持单例,但是规定谈是一个应用Applcation scope下的单例,那么集群部署多台机器也应该全部属于一个Applcation。Although the specification does not cover clustering support, it is very likely that most vendors will make Singletons cluster-safe。(
http://www.theserverside.com/tt/articles/article.tss?l=NewFeaturesinEJB3-1)

正如我以前担心一样,引入单例就容易导致并发问题,需要小心处理,JavaEE 6为Singleton的引入提供了辅助的大量注解:
1.如@Startup,你可以在应用启动时,象auotexec,bat那样单例启动一些工作。
2.并发,有container-managed concurrency 和 bean-managed concurrency.这就复杂了,从这个单例带来的复杂性也可以想象,当初Spring冒然引入单例带来棘手问题,这也是我当时担心反感的理由,没有做到象今天EJB3.1中不但做到拉了屎,还要负责擦屁股。

引入单例的另外一个最大好处就是:面向内存编程,我一直提倡的面向缓存状态编程的模式就可以在JavaEE标准中实现,正如我在JiveJdon中实现并倡导的那样,使用并发锁模式性能要优于铁板一块的事务机制。

具体EJB 3.1 Singleton处理见这里:
http://java.sun.com/javaee/6/docs/tutorial/doc/gipvi.html


[该贴被banq于2009-08-11 20:15修改过]

当时因为EJB是单线程模型的,要求每一个时刻,某一个实例对象只能被一个线程使用,所以采用了实例池的方式来管理stateless session bean的生命周期。

对于单例设计模式,在使用EJB的时候回造成很多微妙的不容易察觉的问题,而这些问题都是因为Java的类装载器引起的,尤其是当你web容器和ejb容器的类加载各自加在了一份单例类的时候,这个问题更加严重。

另外单例必然引起一个问题,那就是多线程共享访问的问题,这个问题如果对于无状态的组件,不涉及共享状态的话,单例也无妨,毕竟也可以通过控制容器的线程数达到控制系统过载而宕机的问题,而一般的无状态的组件一般都是一些功能性的组件,但是如果有状态的话,就要小心了,就需要通过各种并发编程技术来提高系统的并发性,这个时候聚合就起了很重要的作用。所以,

总结两点:
1 对于无状态的功能性组件,无需共享状态,所以单例没问题,这个时候我们可以称之为“容器管理的单例”。

2 对于并发访问的业务对象,我们就需要进行DDD,或者类似概念建模,通过聚合来控制访问,此时的单例我们可以称之为“缓存管理的单例”。

说的好!