Spring的並發問題——有狀態Bean和無狀態Bean


一、有狀態和無狀態

有狀態會話bean   :每個用戶有自己特有的一個實例,在用戶的生存期內,bean保持了用戶的信息,即“有狀態”;一旦用戶滅亡(調用結束或實例結束),bean的生命期也告結束。即每個用戶最初都會得到一個初始的bean。簡單來說,有狀態就是有數據存儲功能。有狀態對象(Stateful Bean),就是有實例變量的對象 ,可以保存數據,是非線程安全的。
無狀態會話bean   :bean一旦實例化就被加進會話池中,各個用戶都可以共用。即使用戶已經消亡,bean   的生命期也不一定結束,它可能依然存在於會話池中,供其他用戶調用。由於沒有特定的用戶,那么也就不能保持某一用戶的狀態,所以叫無狀態bean。但無狀態會話bean   並非沒有狀態,如果它有自己的屬性(變量),那么這些變量就會受到所有調用它的用戶的影響,這是在實際應用中必須注意的。簡單來說,無狀態就是一次操作,不能保存數據。無狀態對象(Stateless Bean),就是沒有實例變量的對象 .不能保存數據,是不變類,是線程安全的。

package com.sw;

public class TestManagerImpl implements TestManager{
    private User user;    //有一個記錄信息的實例

    public void deleteUser(User e) throws Exception {
        user = e ;           //1
        prepareData(e);
    }

    public void prepareData(User e) throws Exception {
        user = getUserByID(e.getId());            //2
        .....
        //使用user.getId();                       //3
        .....
        .....
    }    
}
一個有狀態的bean
 
二、解決有狀態bean的線程安全問題
Spring對bean的配置中有四種配置方式,我們只說其中兩種:singleton單例模式、prototype原型模式。
<bean id="testManager" class="com.sw.TestManagerImpl" scope="singleton" />

<bean id="testManager" class="com.sw.TestManagerImpl" scope="prototype" />

默認的配置是singleton。

singleton表示該bean全局只有一個實例。

prototype表示該bean在每次被注入的時候,都要重新創建一個實例,這種情況適用於有狀態的Bean。

 

如果對有狀態的bean使用了singleton的話會出現線程安全問題。

例如上面的例子

如果有兩個用戶同時訪問

假定為user1,user2

當user1 調用到程序中的1步驟的時候,該Bean的私有變量user被付值為user1

當user1的程序走到2步驟的時候,該Bean的私有變量user被重新付值為user1_create

理想的狀況,當user1走到3步驟的時候,私有變量user應該為user1_create;

但如果在user1調用到3步驟之前,user2開始運行到了1步驟了,由於單態的資源共享,則私有變量user被修改為user2

這種情況下,user1的步驟3用到的user.getId()實際用到是user2的對象。

對於這種情況我們可以這樣解決

1.將有狀態的bean配置成prototype模式,讓每一個線程都創建一個prototype實例。但是這樣會產生很多的實例消耗較多的內存空間。

2.使用ThreadLocal變量,為每一條線程設置變量副本。

 

使用ThreadLocal的例子:

例如,我們有一個銀行的BankDAO類和一個個人賬戶的PeopleDAO類,現在需要個人向銀行進行轉賬,在PeopleDAO類中有一個賬戶減少的方法,BankDAO類中有一個賬戶增加的方法,那么這兩個方法在調用的時候必須使用同一個Connection數據庫連接對象,如果他們使用兩個Connection對象,則會開啟兩段事務,可能出現個人賬戶減少而銀行賬戶未增加的現象。使用同一個Connection對象的話,在應用程序中可能會設置為一個全局的數據庫連接對象,從而避免在調用每個方法時都傳遞一個Connection對象。問題是當我們把Connection對象設置為全局變量時,你不能保證是否有其他線程會將這個Connection對象關閉,這樣就會出現線程安全問題。解決辦法就是在進行轉賬操作這個線程中,使用ThreadLocal中獲取Connection對象,這樣,在調用個人賬戶減少和銀行賬戶增加的線程中,就能從ThreadLocal中取到同一個Connection對象,並且這個Connection對象為轉賬操作這個線程獨有,不會被其他線程影響,保證了線程安全性。

public class ConnectionHolder { public static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { }; public static Connection getConnection(){ Connection connection = connectionHolder.get(); if(null == connection){ connection = DriverManager.getConnection(DB_URL); connectionHolder.set(connection); } return connection; } }

參考自:https://blog.csdn.net/cs408/article/details/47809271

    https://blog.csdn.net/a236209186/article/details/61460211

    https://blog.csdn.net/u012045045/article/details/84340906

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM