精通有狀態vs無狀態(Stateful vs Stateless)

精通有狀態vs無狀態(Stateful vs Stateless)—Immutable模式之姐妹篇

Peter Wei

 

我相信有不少人還不明白有狀態和無狀態(Stateful and Stateless)的概念,那麼我們今天就來談談有狀態和無狀態,一方面不斷總結提高自我,另一方面兼掃盲。這是Immutable不變模式的姐妹篇,大家可以參照着讀。

Immutable不變模式的分析blog: http://www.javaeye.com/topic/959751

 

基本概念:

有狀態就是有數據存儲功能。有狀態對象(Stateful Bean),就是有實例變量的對象,可以保存數據,是非線程安全的。在不同方法調用間不保留任何狀態。

 

無狀態就是一次操作,不能保存數據。無狀態對象(Stateless Bean),就是沒有實例變量的對象.不能保存數據,是不變類,是線程安全的。

 

代碼更好理解:

 

/**

 * 有狀態bean,state,user等屬性,並且user有存偖功能,是可變的。

 *

 * @author Peter Wei

 *

 */

public class StatefulBean {

 

    public int state;

    // 由於多線程環境下,user是引用對象,是非線程安全的

    public User user;

 

    public int getState() {

       return state;

    }

 

    public void setState(int state) {

       this.state = state;

    }

 

    public User getUser() {

       return user;

    }

 

    public void setUser(User user) {

       this.user = user;

    }

}

 

/**

 * 無狀態bean,不能存偖數據。因爲沒有任何屬性,所以是不可變的。只有一系統的方法操作。

 *

 * @author Peter Wei

 *

 */

public class StatelessBeanService {

 

    // 雖然有billDao屬性,但billDao是沒有狀態信息的,是Stateless Bean.

    BillDao billDao;

 

    public BillDao getBillDao() {

       return billDao;

    }

 

    public void setBillDao(BillDao billDao) {

       this.billDao = billDao;

    }

 

    public List<User> findUser(String Id) {

return null;

    }

}

單例模式中的有狀態和無狀態:

單例類可以是有狀態的(stateful),一個有狀態的單例對象一般也是可變(mutable)單例對象。有狀態的可變的單例對象常常當做狀態庫(repositary)使用。比如一個單例對象TaskCacheSpring中配爲singleton)可以持有一個AtomicLong類型的屬性,用來給一個系統提供一個數值惟一的序列號碼,作爲任務通迅管理的ID生成器。同時,一個單例類也可以持有一個聚集,從而允許存儲多個狀態,如示例中的ExpiringMap緩存任務列表。

代碼示例:

import java.util.concurrent.atomic.AtomicLong;

 

import org.apache.mina.util.ExpiringMap;

 

/**

 * Description: 內存中緩存的實時控制端任務列表.示例有狀態的單例類

 *

 * @author Peter Wei

 * @version 1.0 Dec 2, 2008

 */

public class TaskCache {

 

    // 請求超時

    private short requestTimeout;

 

    // 這個緩存Map是線程安全,並且有定時超時功能

    private ExpiringMap<String, Object> tasksMap = new ExpiringMap<String, Object>();

 

    // 線程安全的原子類,示例有狀態的單例類

    private static AtomicLong seqNo = new AtomicLong(1);

 

    // 示例有狀態的單例類

    public Long nextSeqNo() {

       return seqNo.getAndIncrement();

    }

 

    public void setRequestTimeout(short requestTimeout) {

       this.requestTimeout = requestTimeout;

    }

 

    // 啓動過期檢測

    public void startExpiring() {

       tasksMap.getExpirer().setTimeToLive(requestTimeout);

       tasksMap.getExpirer().startExpiringIfNotStarted();

    }

 

    // 停止過期檢測

    public void stopExpiring() {

       tasksMap.getExpirer().stopExpiring();

    }

 

    // 取任務列表.

    public Object getTasks(String key) {

       return tasksMap.get(key);

    }

 

    // 去除任務列表.

    public Object removeTasks(String key) {

       return tasksMap.remove(key);

    }

 

    // 添加任務列表.

    public void addTasks(String key, Object value) {

       tasksMap.put(key, value);

    }

}

 

單例類也可以是沒有狀態的(stateless),僅用做提供工具性函數的對象。既然是爲了提供工具性函數,也就沒有必要創建多個實例,因此使用單例模式很合適。平常的單例類都是沒有狀態的,這裏就不示例了。一個沒有狀態的單例類也就是不變(Immutable)單例類。關於不變模式,請參考 http://www.javaeye.com/topic/959751

 

EJB中的有狀態與無狀態:

1.Stateful session bean的每個用戶都有自己的一個實例,所以兩者對stateful session bean的操作不會影響對方。另外注意:如果後面需要操作某個用戶的實例,你必須在客戶端緩存BeanStub對象(JSP通常的做法是用Session緩存),這樣在後面每次調用中,容器才知道要提供相同的bean實例。

2.Stateless Session Bean不負責記錄使用者狀態,Stateless Session Bean一旦實例化就被加進會話池中,各個用戶都可以共用。如果它有自己的屬性(變量),那麼這些變量就會受到所有調用它的用戶的影響。

3.從內存方面來看,Stateful Session BeanStateless Session Bean比較,Stateful Session Bean會消耗J2EE Server 較多的內存,然而Stateful Session Bean的優勢卻在於他可以維持使用者的狀態。

 

Spring中的有狀態(Stateful)和無狀態(Stateless)

1.通過上面的分析,相信大家已經對有狀態和無狀態有了一定的理解。無狀態的Bean適合用不變模式,技術就是單例模式,這樣可以共享實例,提高性能。有狀態的Bean,多線程環境下不安全,那麼適合用Prototype原型模式。Prototype: 每次對bean的請求都會創建一個新的bean實例。

2.默認情況下,從Spring bean工廠所取得的實例爲singletonscope屬性爲singleton,容器只存在一個共享的bean實例。

3.理解了兩者的關係,那麼scope選擇的原則就很容易了:有狀態的bean都使用prototype作用域,而對無狀態的bean則應該使用singleton作用域。

4.Service層、Dao層用默認singleton就行,雖然Service類也有dao這樣的屬性,但dao這些類都是沒有狀態信息的,也就是相當於不變(immutable)類,所以不影響。Struts2中的Action因爲會有UserBizEntity這樣的實例對象,是有狀態信息的,在多線程環境下是不安全的,所以Struts2默認的實現是Prototype模式。在Spring中,Struts2Action中,scope要配成prototype作用域。

 

ServletStruts中的有狀態和無狀態:

1.Servlet體系結構是建立在Java多線程機制之上的,它的生命週期是由Web 容器負責的。一個Servlet類在Application中只有一個實例存在,也就是有多個線程在使用這個實例。這是單例模式的應用。無狀態的單例是線程安全的,但我們如果在Servlet裏用了實例變量,那麼就變成有狀態了,是非線程安全的。如下面的用法就是不安全的,因爲user,out都是有狀態信息的。

/**

 * 非線程安全的Servlet

 * @author Peter Wei

 *

 */

public class UnSafeServlet HttpServlet{

   

    User user;

    PrintWriter out;

   

    public void doGet (HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException{

       //do something...

    }

}

Out,Request,Response,Session,Config,Page,PageContext是線程安全的,Application在整個系統內被使用,所以不是線程安全的.

 

2.Struts1也是基於單例模式實現,也就是隻有一個Action實例供多線程使用。默認的模式是前臺頁面數據通過actionForm傳入,在action中的excute方法接收,這樣action是無狀態的,所以一般情況下Strunts1是線程安全的。如果Action中用了實例變量,那麼就變成有狀態了,同樣是非線程安全的。像下面這樣就是線程不安全的。

/**

 * 非線程安全的Struts1示例

 *

 * @author Peter Wei

 *

 */

public class UnSafeAction1 extends Action {

 

    // 因爲Struts1是單例實現,有狀態情況下,對象引用是非線程安全的

    User user;

 

    public void execute() {

       // do something...

    }

 

    public User getUser() {

       return user;

    }

 

    public void setUser(User user) {

       this.user = user;

    }

}  

 

3.Struts2默認的實現是Prototype模式。也就是每個請求都新生成一個Action實例,所以不存在線程安全問題。需要注意的是,如果由Spring管理action的生命週期, scope要配成prototype作用域。

 

4.如何解決ServletStruts1的線程安全問題,當我們能比較好的理解有狀態和無狀態的原理,自然很容易得出結論:不要使用有狀態的bean,也就是不要用實例變量。如果用,就要用prototype模式。Struts1 user guide裏有: Only Use Local Variables - The most important principle that aids in thread-safe coding is to use only local variables, not instance variables , in your Action class.

 

總結:

Stateless無狀態用單例Singleton模式,Stateful有狀態就用原型Prototype模式。

Stateful 有狀態是多線程編碼的天敵,所以在開發中儘量用Stateless無狀態,無狀態是不變(immutable)模式的應用,有很多優點:不用管線程和同步的問題,如果值是不可變的,程序不用擔心多個線程改變共享狀態,所以可以避免線程競爭的bugs. 因爲沒有競爭,就不用用locks等機制,所以無狀態的不變機制,也可以避免產生死鎖現象。

 

國外一些哥們的觀點:

Immutable objects may not be altered after their creation. So: Yes, they are some kind of stateless.As immutable objects can not be changed, there is no need for locking - reading access to objects is always threadsafe (when not modifying variables). Therefore, real immutable objects are always threadsafe.

 

Rod Johnson大叔的觀點:

Stateless or Stateful?

Service objects will usually be stateless. Stateless service layers are highly scalable: They pose no replication issues and there is no need to allocate additional resources for every client. (Remember that one of
the key motivations of a middle tier is to share resources between multiple clients.) It is also much easier for stateless service layers to support remote clients, if necessary. A stateless service layer is one concession of object orientation that I find not too painful.

此處省去N個字。

If possible, design applications to use a stateless service layer. Hold state in the web tier, rather than in the business logic tier, if possible.

 

關於不變模式分析,請參考 http://www.javaeye.com/topic/959751

發佈了29 篇原創文章 · 獲贊 6 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章