电竞比分网-中国电竞赛事及体育赛事平台

分享

JavaEE組件的并發(fā)與無狀態(tài)

 CevenCheng 2011-09-05

JavaEE組件的并發(fā)與無狀態(tài)

sulong 于 2009-01-05說兩句 ?

并發(fā)與線程安全

串行運行時正確的程序在并發(fā)運行時可能會出錯,這是由于并發(fā)運行的多個任務(wù)(進程或線程)之間共享了變量。在Java EE環(huán)境下很多的組件最終都是在多線程環(huán)境下并發(fā)運行的,應(yīng)該牢記住這一點,避免發(fā)生詭異的錯誤。在上個項目中,我們的程序就出現(xiàn)過詭異的錯誤,在并發(fā)量小的測試環(huán)境下,它很少出現(xiàn),但是隨著并發(fā)量的增大,它出現(xiàn)的次數(shù)增多。經(jīng)查,發(fā)現(xiàn)如下類似代碼:

    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()產(chǎn)生的 someVariable,但是Servlet可能被多個線程共享,上面的代碼不能正常工作。當(dāng)Servlet容器用thread1和thread2兩個線程來服務(wù)兩個請求request1和request2,而thread1和thread2使用了同一個SomeServlet對象,如果JVM在 SomeServlet在執(zhí)行到method1()后和method2()前時發(fā)生線程間的切換,那么就很可能導(dǎo)致出錯,比如:thread1.method1()->thread2.method1()->thread1.method2()->thread2.method2()。實例變量和類變量都是在線程間共享的,而方法內(nèi)的局部變量和參數(shù)是不會被共享的,所以要處理這個問題,最簡單的方法是通過方法參數(shù)來傳遞 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等并發(fā)訪問控制機制也可以解決這個問題,但是我覺得上面的方式是最直觀的。也許有人會說后面的代碼比前面的看起來要難看,不夠面向?qū)ο?。這里不討論怎樣才是面向?qū)ο螅蛘呙嫦驅(qū)ο蠛貌缓???傊笳呤钦_的,前者是錯誤的,沒有正確性的程序是沒有意義的。

被過線程共享而不會出錯的對象,被稱之為線程安全的。顯然Servlet不是線程安全的,而且還有很多組件也不是線程安全的,比如HttpSession。在上個項目中,我們使用JAXB來處理XML,每次使用時都創(chuàng)建一個JAXBContext對象,后來發(fā)現(xiàn)創(chuàng)建JAXBContext的代價是相當(dāng)?shù)母?,于是我們想把它緩存起來在整個應(yīng)用程序范圍內(nèi)使用,幸好我們使用的JAXBContext的實現(xiàn)是線程安全的,可以放心的被多個線程共享。

有狀態(tài)與無狀態(tài)

有狀態(tài)組件是這樣一種組件,組件調(diào)用者的返回結(jié)果會依賴于這個組件之前或正在受到的調(diào)用。無狀態(tài)的組件則相反,調(diào)用者的返回結(jié)果只和本次調(diào)用相關(guān),所有調(diào)用者的調(diào)用過程不會影響到其他調(diào)用者。舉例來說,someComponent組件的getLatestCaller()返回上一次的調(diào)用者,這個行為的返回值總會和上一次的調(diào)用者相關(guān),這樣的組件就是有狀態(tài)的。假如有個組件math有個方法為add(x,y),返回x+y的值,那么無論之前被哪個調(diào)用者調(diào)用過,math.add()的返回值只和本次調(diào)用的參數(shù)相關(guān),這樣的組件是無狀態(tài)的。無狀態(tài)的組件是線程安全的,因為被別的調(diào)用者調(diào)用并不會影響到本次的調(diào)用結(jié)果。

除非有必要,否則應(yīng)當(dāng)盡量把你的程序組件實現(xiàn)成無狀態(tài)的。在spring, seam, ejb里都有無狀態(tài)的組件。不同的框架在如何實現(xiàn)無狀態(tài)方面各不相同。最簡單的實現(xiàn)方法就是為無狀態(tài)組件做一個包裝,每次調(diào)用組件的方法時,都由包裝類生成一個新的對象來處理,這樣實際上就不再任何的調(diào)用者之間共享被調(diào)用者的實例,實現(xiàn)了無狀態(tài)。比如:

    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);
        }
    }

如果實例化組件的代價是昂貴的,用一個對象池緩存組件實例,并在每次從池中取對象時清空對象的狀態(tài)可能是個更好的辦法。其實只要遵循簡單的原則,就很容易實現(xiàn)無狀態(tài),就是不使用實例變量或類變量,或者只使用只讀的實例變量或只讀類變量。這樣的組件用單例就可以實先無狀態(tài)。

由于實現(xiàn)上的差別,有些框架根本就不會做過多的努力來保證組件的無狀態(tài)性,相反他們把責(zé)任交給了程序員。作為組件的創(chuàng)作者,如果你確定你要的是無狀態(tài)的組件,那么只使用只讀的實例變量或類變量,是個明智的選擇。

相關(guān)文章:

  1. JAXB中如何利用繼承生成XML
  2. 當(dāng)return遇到finally
  3. 最快的計算子字符串出現(xiàn)次數(shù)程序
  4. 用hibernate映射時遇到的問題
  5. 靜態(tài)域的作用范圍
1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多