一、有狀態和無狀態
有狀態會話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 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