分享

JavaEE组件的并发与无状态

 CevenCheng 2011-09-05

JavaEE组件的并发与无状态

sulong 于 2009-01-05说两句 ?

并发与线程安全

串行运行时正确的程序在并发运行时可能会出错,这是由于并发运行的多个任务(进程或线程)之间共享了变量。在Java EE环境下很多的组件最终都是在多线程环境下并发运行的,应该牢记住这一点,避免发生诡异的错误。在上个项目中,我们的程序就出现过诡异的错误,在并发量小的测试环境下,它很少出现,但是随着并发量的增大,它出现的次数增多。经查,发现如下类似代码:

    public class SomeServlet extends HttpServlet {
        private SomeType someVariable;

        public void doPost(HttpServletRequest req, HttpServletRespons rsp) {
            method1();
            method2();
        }

        void method1() {
            ...
            someVariable = someValue;
            ...
        }

        void method2() {
            ...
            someVariable = someValue;
            ...
        }
    }

method1() 和method2()都访问到了someVariable,而且程序员的本意是想在method2()使用由method1()产生的 someVariable,但是Servlet可能被多个线程共享,上面的代码不能正常工作。当Servlet容器用thread1和thread2两个线程来服务两个请求request1和request2,而thread1和thread2使用了同一个SomeServlet对象,如果JVM在 SomeServlet在执行到method1()后和method2()前时发生线程间的切换,那么就很可能导致出错,比如:thread1.method1()->thread2.method1()->thread1.method2()->thread2.method2()。实例变量和类变量都是在线程间共享的,而方法内的局部变量和参数是不会被共享的,所以要处理这个问题,最简单的方法是通过方法参数来传递 someVariable,而不是通过实例变量。所以这段程序改成这样就可以了:

    public class SomeServlet extends HttpServlet {

        public void doPost(HttpServletRequest req, HttpServletRespons rsp) {
            SomeType someVariable = method1();
            method2(someVariable );
        }

        SomeType someVariablemethod1() {
            ...
            return someValue;
        }

        void method2(SomeType someVariable) {
            ...
            someVariable = someValue;
            ...
        }
    }

通过synchronized等并发访问控制机制也可以解决这个问题,但是我觉得上面的方式是最直观的。也许有人会说后面的代码比前面的看起来要难看,不够面向对象。这里不讨论怎样才是面向对象,或者面向对象好不好。总之后者是正确的,前者是错误的,没有正确性的程序是没有意义的。

被过线程共享而不会出错的对象,被称之为线程安全的。显然Servlet不是线程安全的,而且还有很多组件也不是线程安全的,比如HttpSession。在上个项目中,我们使用JAXB来处理XML,每次使用时都创建一个JAXBContext对象,后来发现创建JAXBContext的代价是相当的高,于是我们想把它缓存起来在整个应用程序范围内使用,幸好我们使用的JAXBContext的实现是线程安全的,可以放心的被多个线程共享。

有状态与无状态

有状态组件是这样一种组件,组件调用者的返回结果会依赖于这个组件之前或正在受到的调用。无状态的组件则相反,调用者的返回结果只和本次调用相关,所有调用者的调用过程不会影响到其他调用者。举例来说,someComponent组件的getLatestCaller()返回上一次的调用者,这个行为的返回值总会和上一次的调用者相关,这样的组件就是有状态的。假如有个组件math有个方法为add(x,y),返回x+y的值,那么无论之前被哪个调用者调用过,math.add()的返回值只和本次调用的参数相关,这样的组件是无状态的。无状态的组件是线程安全的,因为被别的调用者调用并不会影响到本次的调用结果。

除非有必要,否则应当尽量把你的程序组件实现成无状态的。在spring, seam, ejb里都有无状态的组件。不同的框架在如何实现无状态方面各不相同。最简单的实现方法就是为无状态组件做一个包装,每次调用组件的方法时,都由包装类生成一个新的对象来处理,这样实际上就不再任何的调用者之间共享被调用者的实例,实现了无状态。比如:

    public interface SomeInterface {
        public SomeType doMyBusinness(SomeArg arg);
    }

    public class SomeStatlessComponent implements SomeInterface {
        public SomeType doMyBusinness(SomeArg arg) {
           ...
        }
    }

    public class SomeStatlessComponentWrapper implements SomeInterface {
        public SomeType doMyBusinness(SomeArg arg) {
           return new SomeStatlessComponent().doMyBusinness(arg);
        }
    }

如果实例化组件的代价是昂贵的,用一个对象池缓存组件实例,并在每次从池中取对象时清空对象的状态可能是个更好的办法。其实只要遵循简单的原则,就很容易实现无状态,就是不使用实例变量或类变量,或者只使用只读的实例变量或只读类变量。这样的组件用单例就可以实先无状态。

由于实现上的差别,有些框架根本就不会做过多的努力来保证组件的无状态性,相反他们把责任交给了程序员。作为组件的创作者,如果你确定你要的是无状态的组件,那么只使用只读的实例变量或类变量,是个明智的选择。

相关文章:

  1. JAXB中如何利用继承生成XML
  2. 当return遇到finally
  3. 最快的计算子字符串出现次数程序
  4. 用hibernate映射时遇到的问题
  5. 静态域的作用范围
1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多