pring中管理的bean實例默認情況下是單例的[sigleton類型],就還有prototype類型
按其作用域來講有sigleton,prototype,request,session,global session。
spring中的單例與設計模式里面的單例略有不同,設計模式的單例是在整個應用中只有一個實例,而spring中的單例是在一個IoC容器中就只有一個實例。
但spring中的單例也不會影響應用的並發訪問,【不會出現各個線程之間的等待問題,或是死鎖問題】因為大多數時候客戶端都在訪問我們應用中的業務對象,而這些業務對象並
沒有做線程的並發限制,只是在這個時候我們不應該在業務對象中設置那些容易造成出錯的成員變量,在並發訪問時候這些成員變量將會是並發線程中的共享對象,那么這個時候
就會出現意外情況。
那么我們的Eic-server的所有的業務對象中的成員變量如,在Dao中的xxxDao,或controller中的xxxService,都會被多個線程共享,那么這些對象不會出現同步問題嗎,比如會造
成數據庫的插入,更新異常?
還有我們的實體bean,從客戶端傳遞到后台的controller-->service-->Dao,這一個流程中,他們這些對象都是單例的,那么這些單例的對象在處理我們的傳遞到后台的實體bean不
會出問題嗎?
答:[實體bean不是單例的],並沒有交給spring來管理,每次我們都手動的New出來的【如EMakeType et = new EMakeType();】,所以即使是那些處理我們提交數據的業務處理類
是被多線程共享的,但是他們處理的數據並不是共享的,數據時每一個線程都有自己的一份,所以在數據這個方面是不會出現線程同步方面的問題的。但是那些的在Dao中的
xxxDao,或controller中的xxxService,這些對象都是單例那么就會出現線程同步的問題。但是話又說回來了,這些對象雖然會被多個進程並發訪問,可我們訪問的是他們里面的方
法,這些類里面通常不會含有成員變量,那個Dao里面的ibatisDao是框架里面封裝好的,已經被測試,不會出現線程同步問題了。所以出問題的地方就是我們自己系統里面的業務
對象,所以我們一定要注意這些業務對象里面千萬不能要獨立成員變量,否則會出錯。
所以我們在應用中的業務對象如下例子;
controller中的成員變量List和paperService:
public class TestPaperController extends BaseController {
private static final int List = 0;
@Autowired
@Qualifier("papersService")
private TestPaperService papersService ;
public Page queryPaper(int pageSize, int page,TestPaper paper) throws EicException{
RowSelection localRowSelection = getRowSelection(pageSize, page);
List<TestPaper> paperList = papersService.queryPaper(paper,localRowSelection);
Page localPage = new Page(page, localRowSelection.getTotalRows(),
paperList);
return localPage;
}
service里面的成員變量ibatisEntityDao:
@SuppressWarnings("unchecked")
@Service("papersService")
@Transactional(rollbackFor = { Exception.class })
public class TestPaperServiceImpl implements TestPaperService {
@Autowired
@Qualifier("ibatisEntityDao")
private IbatisEntityDao ibatisEntityDao;
private static final String NAMESPACE_TESTPAPER = "com.its.exam.testpaper.model.TestPaper";
private static final String BO_NAME[] = { "試卷倉庫" };
private static final String BO_NAME2[] = { "試卷配置試題" };
private static final String BO_NAME1[] = { "試卷試題類型" };
private static final String NAMESPACE_TESTQUESTION="com.its.exam.testpaper.model.TestQuestion";
public List<TestPaper> queryPaper(TestPaper paper,RowSelection paramRowSelection) throws EicException{
try {
return (List<TestPaper>) ibatisEntityDao.queryForListWithPage(
NAMESPACE_TESTPAPER, "queryPaper", paper,paramRowSelection);
} catch (Exception exception) {
exception.printStackTrace();
throw new EicException(exception, "eic", "0001", BO_NAME);
}
}
由上面可以看出,雖然我們這個應用里面含有成員變量,但是並不會出現線程同步方面的問題,因為,controller里面的成員變量private TestPaperService papersService ;之
所以會成為成員變量,我們的目的是注入,將其實例化進而訪問里面的方法,private static final int List = 0;是final的不會被改變。
service里面的private IbatisEntityDao ibatisEntityDao;是框架本身的線程同步問題已解決【其解決方案很有可能就是使用ThreadLocal,見下面】。
這下面的bean 一個是通過BeanFactory getBean得到,一個是業務對象testPaper.getClass(),得到,通過不同客戶端的瀏覽器訪問,可得到下面結論,
springIoC容器管理的bean就是單例,因為不同的訪問均得到相同的對象【在應用開啟的狀態下,不重新啟動應用下,即在同一次的應用運行中】
-------------------------spring 中的sigleton ,這才是真正的整個應用下面就一個實例:class
com.its.exam.testpaper.service.impl.TestPaperServiceImpl$$EnhancerByCGLIB$$584b889d
-------------------------spring 中的sigleton ,這才是真正的整個應用下面就一個實例:class
com.its.exam.testpaper.service.impl.TestPaperServiceImpl$$EnhancerByCGLIB$$584b889d
-------------------------spring 中的sigleton ,這才是真正的整個應用下面就一個實例:class
com.its.exam.testpaper.service.impl.TestPaperServiceImpl$$EnhancerByCGLIB$$584b889d
-------------------------spring 中的sigleton ,這才是真正的整個應用下面就一個實例:class
com.its.exam.testpaper.service.impl.TestPaperServiceImpl$$EnhancerByCGLIB$$584b889d
Spring框架對單例的支持是采用單例注冊表的方式進行實現的,詳見“Spring設計模式——單例模式”這篇文章。
至於spring如何實現那些個有狀態bean[如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder]的線程安全,如下原理:詳見“ThreadLocal百度百科”,還可以參考網上這篇文章:“淺談Spring聲明式事務管理ThreadLocal和JDKProxy”。
雖然代碼清單9‑3這個ThreadLocal實現版本顯得比較幼稚,但它和JDK所提供的ThreadLocal類在實現思路上是相近的。
在Java的多線程編程中,為保證多個線程對共享變量的安全訪問,通常會使用synchronized來保證同一時刻只有一個線程對共享變量進行操作。但在有些情況下,synchronized不能保證多線程對共享變量的正確讀寫。例如類有一個類變量,該類變量會被多個類方法讀寫,當多線程操作該類的實例對象時,如果線程對類變量有讀取、寫入操作就會發生類變量讀寫錯誤,即便是在類方法前加上synchronized也無效,因為同一個線程在兩次調用方法之間時鎖是被釋放的,這時其它線程可以訪問對象的類方法,讀取或修改類變量。這種情況下可以將類變量放到ThreadLocal類型的對象中,使變量在每個線程中都有獨立拷貝,不會出現一個線程讀取變量時而被另一個線程修改的現象。
為了說明多線程訪問對於類變量和ThreadLocal變量的影響,QuerySvc中分別設置了類變量sql和ThreadLocal變量,使用時先創建 QuerySvc的一個實例對象,然后產生多個線程,分別設置不同的sql實例對象,然后再調用execute方法,讀取sql的值,看是否是set方法中寫入的值。這種場景類似web應用中多個請求線程攜帶不同查詢條件對一個servlet實例的訪問,然后servlet調用業務對象,並傳入不同查詢條件,最后要保證每個請求得到的結果是對應的查詢條件的結果。
先創建一個QuerySvc實例對象,然后創建若干線程來調用QuerySvc的set和execute方法,每個線程傳入的sql都不一樣,從運行結果可以看出sql變量中值不能保證在execute中值和set設置的值一樣,在 web應用中就表現為一個用戶查詢的結果不是自己的查詢條件返回的結果,而是另一個用戶查詢條件的結果;而ThreadLocal中的值總是和set中設置的值一樣,這樣通過使用ThreadLocal獲得了線程安全性。
如果一個對象要被多個線程訪問,而該對象存在類變量被不同類方法讀寫,為獲得線程安全,可以用ThreadLocal來替代類變量。
同步機制的比較 ThreadLocal和線程同步機制相比有什么優勢呢?ThreadLocal和線程同步機制都是為了解決多線程中相同變量的訪問沖突問題。
在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析什么時候對變量進行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。
而ThreadLocal則從另一個角度來解決多線程的並發訪問。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。
由於ThreadLocal中可以持有任何類型的對象,低版本JDK所提供的get()返回的是Object對象,需要強制類型轉換。但JDK 5.0通過泛型很好的解決了這個問題,在一定程度地簡化ThreadLocal的使用,代碼清單 9 2就使用了JDK 5.0新的ThreadLocal版本。
概括起來說,對於多線程資源共享的問題,同步機制采用了“以時間換空間”的方式,而ThreadLocal采用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。
Spring使用ThreadLocal解決線程安全問題
我們知道在一般情況下,只有無狀態的Bean才可以在多線程環境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態采用ThreadLocal進行處理,讓它們也成為線程安全的狀態,因為有狀態的Bean就可以在多線程中共享了。
一般的Web應用划分為展現層、服務層和持久層三個層次,在不同的層中編寫對應的邏輯,下層通過接口向上層開放功能調用。在一般情況下,從接收請求到返回響應所經過的所有程序調用都同屬於一個線程,如圖9‑2所示:
這樣你就可以根據需要,將一些非線程安全的變量以ThreadLocal存放,在同一次請求響應的調用線程中,所有關聯的對象引用到的都是同一個變量。
由於①處的conn是成員變量,因為addTopic()方法是非線程安全的,必須在使用時創建一個新TopicDao實例(非singleton)。下面使用ThreadLocal對conn這個非線程安全的“狀態”進行改造:
代碼清單4 TopicDao:線程安全
import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {
①使用ThreadLocal保存Connection變量
private static ThreadLocal connThreadLocal = new ThreadLocal();
public static Connection getConnection(){
②如果connThreadLocal沒有本線程對應的Connection創建一個新的Connection,
並將其保存到線程本地變量中。
if (connThreadLocal. get() == null) {
Connection conn = ConnectionManager.getConnection();
connThreadLocal.set(conn);
return conn;
}else{
return connThreadLocal. get();③直接返回線程本地變量
}
}
public void addTopic() {
④從ThreadLocal中獲取線程對應的Connection
Statement stat = getConnection().createStatement();
}
}
不同的線程在使用TopicDao時,先判斷connThreadLocal.是否是null,如果是null,則說明當前線程還沒有對應的Connection對象,這時創建一個Connection對象並添加到本地線程變量中;如果不為null,則說明當前的線程已經擁有了Connection對象,直接使用就可以了。這樣,就保證了不同的線程使用線程相關的Connection,而不會使用其它線程的Connection。因此,這個TopicDao就可以做到singleton共享了。
