spring單例、執行緒安全、事務等疑惑 收集
阿新 • • 發佈:2019-01-06
文章轉載於:
http://haidaoqi3630.iteye.com/blog/1920944
http://www.cnblogs.com/davidwang456/p/3832949.html
Spring的事務傳遞機制:
PROPAGATION_REQUIRED 如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。 PROPAGATION_SUPPORTS 支援當前事務,如果當前沒有事務,就以非事務方式執行。 PROPAGATION_MANDATORY 使用當前的事務,如果當前沒有事務,就丟擲異常。 PROPAGATION_REQUIRES_NEW 新建事務,如果當前存在事務,把當前事務掛起。 PROPAGATION_NOT_SUPPORTED 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。 PROPAGATION_NEVER 以非事務方式執行,如果當前存在事務,則丟擲異常。 PROPAGATION_NESTED 如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與 PROPAGATION_REQUIRED 類似的操作。
spring中管理的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是框架裡面封裝好的,已經被測試,不會出現執行緒同步問題了。所以出問題的地方就是我們自己系統裡面的業務物件,所以我們一定要注意這些業務物件裡面千萬不能要獨立成員變數,否則會出錯。
Spring中由容器託管的類如果沒有特殊宣告(scope = “prototype”),則預設為單列模式,當多使用者同時請求一個服務時,容器會給每一個請求分配一個執行緒,這是多個執行緒會併發執行該請求多對應的業務邏輯(成員方法),此時就要注意了,如果該處理邏輯中有對該單列狀態的修改(成員變數),則必須考慮執行緒同步問題;否則由於在業務邏輯中執行所需的區域性變數會分配在棧空間中,所以不需要同步。
其實函式本身是程式碼,程式碼是隻讀的,無論多少個執行緒同時調都無所謂(因為只是讀的嘛),但是函式中肯定是要用到資料的,如果資料是函式引數、區域性變數,那麼這些資料都是存在每個執行緒自己的棧上的,同時呼叫是沒有關係的,不會涉及到執行緒安全資源共享的問題。 但是如果使用到了全域性靜態變數或者類的成員變數的時候。就會出現資料安全的問題,還有,如果我們的成員變數在函式體內如果只進行讀操作,不進行寫操作,也是執行緒安全的。
簡而言之一句話,單例的方法在同一個時刻是可以被兩個執行緒同時呼叫的。我們在做程式的時候要儘可能少的使用類的成員變數,如果使用成員變數,儘量保證只對成員變數進行讀操作
當我們的很多使用者去修改自己的資訊的時候,使用者執行緒會通過呼叫dao(dao 我們都是給注入有配連結池的資料來源的),dao 會拿到連結池中的一個連結,將我們要處理的資訊(sql語句等)交付個數據庫,資料庫會按照自己的多執行緒處理機制完成執行緒的同步,然後進行資料安全處理,執行緒在完成資料處理後會將佔有的連結放回到連結池中。
——————————————————————————————————————
以下來自http://www.cnblogs.com/doit8791/p/4093808.html
Spring框架裡的bean,或者說元件,獲取例項的時候都是預設的單例模式,這是在多執行緒開發的時候要尤其注意的地方。
單例模式的意思就是隻有一個例項。單例模式確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項。這個類稱為單例類。
當多使用者同時請求一個服務時,容器會給每一個請求分配一個執行緒,這是多個執行緒會併發執行該請求多對應的業務邏輯(成員方法),此時就要注意了,如果該處理邏輯中有對該單列狀態的修改(體現為該單列的成員屬性),則必須考慮執行緒同步問題
同步機制的比較 ThreadLocal和執行緒同步機制相比有什麼優勢呢?ThreadLocal和執行緒同步機制都是為了解決多執行緒中相同變數的訪問衝突問題。
在同步機制中,通過物件的鎖機制保證同一時間只有一個執行緒訪問變數。這時該變數是多個執行緒共享的,使用同步機制要求程式慎密地分析什麼時候對變數進行讀寫,什麼時候需要鎖定某個物件,什麼時候釋放物件鎖等繁雜的問題,程式設計和編寫難度相對較大。
而ThreadLocal則從另一個角度來解決多執行緒的併發訪問。ThreadLocal會為每一個執行緒提供一個獨立的變數副本,從而隔離了多個執行緒對資料的訪問衝突。因為每一個執行緒都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。ThreadLocal提供了執行緒安全的共享物件,在編寫多執行緒程式碼時,可以把不安全的變數封裝進ThreadLocal。
由於ThreadLocal中可以持有任何型別的物件,低版本JDK所提供的get()返回的是Object物件,需要強制型別轉換。但JDK 5.0通過泛型很好的解決了這個問題,在一定程度地簡化ThreadLocal的使用
概括起來說,對於多執行緒資源共享的問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的方式。前者僅提供一份變數,讓不同的執行緒排隊訪問,而後者為每一個執行緒都提供了一份變數,因此可以同時訪問而互不影響。
Spring使用ThreadLocal解決執行緒安全問題
我們知道在一般情況下,只有無狀態的Bean才可以在多執行緒環境下共享,在Spring中,絕大部分Bean都可以宣告為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非執行緒安全狀態採用ThreadLocal進行處理,讓它們也成為執行緒安全的狀態,因為有狀態的Bean就可以在多執行緒中共享了。
一般的Web應用劃分為展現層、服務層和持久層三個層次,在不同的層中編寫對應的邏輯,下層通過介面向上層開放功能呼叫。在一般情況下,從接收請求到返回響應所經過的所有程式呼叫都同屬於一個執行緒
ThreadLocal是解決執行緒安全問題一個很好的思路,它通過為每個執行緒提供一個獨立的變數副本解決了變數併發訪問的衝突問題。在很多情況下,ThreadLocal比直接使用synchronized同步機制解決執行緒安全問題更簡單,更方便,且結果程式擁有更高的併發性。
如果你的程式碼所在的程序中有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。如果每次執行結果和單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。 或者說:一個類或者程式所提供的介面對於執行緒來說是原子操作或者多個執行緒之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題。 執行緒安全問題都是由全域性變數及靜態變數引起的。
若每個執行緒中對全域性變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全域性變數是執行緒安全的;若有多個執行緒同時執行寫操作,一般都需要考慮執行緒同步,否則就可能影響執行緒安全。
1) 常量始終是執行緒安全的,因為只存在讀操作。
2)每次呼叫方法前都新建一個例項是執行緒安全的,因為不會訪問共享的資源。
3)區域性變數是執行緒安全的。因為每執行一個方法,都會在獨立的空間建立區域性變數,它不是共享的資源。區域性變數包括方法的引數變數和方法內變數。
有狀態就是有資料儲存功能。有狀態物件(Stateful Bean),就是有例項變數的物件 ,可以儲存資料,是非執行緒安全的。在不同方法呼叫間不保留任何狀態。
無狀態就是一次操作,不能儲存資料。無狀態物件(Stateless Bean),就是沒有例項變數的物件 .不能儲存資料,是不變類,是執行緒安全的。
有狀態物件:
無狀態的Bean適合用不變模式,技術就是單例模式,這樣可以共享例項,提高效能。有狀態的Bean,多執行緒環境下不安全,那麼適合用Prototype原型模式。Prototype: 每次對bean的請求都會建立一個新的bean例項。
Struts2預設的實現是Prototype模式。也就是每個請求都新生成一個Action例項,所以不存線上程安全問題。需要注意的是,如果由Spring管理action的生命週期, scope要配成prototype作用域。
二、執行緒安全案例:
SimpleDateFormat(下面簡稱sdf)類內部有一個Calendar物件引用,它用來儲存和這個sdf相關的日期資訊,例如sdf.parse(dateStr), sdf.format(date) 諸如此類的方法引數傳入的日期相關String, Date等等, 都是交友Calendar引用來儲存的.這樣就會導致一個問題,如果你的sdf是個static的, 那麼多個thread 之間就會共享這個sdf, 同時也是共享這個Calendar引用, 並且, 觀察 sdf.parse() 方法,你會發現有如下的呼叫:
Date parse() {
calendar.clear(); // 清理calendar
… // 執行一些操作, 設定 calendar 的日期什麼的
calendar.getTime(); // 獲取calendar的時間
}
這裡會導致的問題就是, 如果 執行緒A 呼叫了 sdf.parse(), 並且進行了 calendar.clear()後還未執行calendar.getTime()的時候,執行緒B又呼叫了sdf.parse(), 這時候執行緒B也執行了sdf.clear()方法, 這樣就導致執行緒A的的calendar資料被清空了(實際上A,B的同時被清空了). 又或者當 A 執行了calendar.clear() 後被掛起, 這時候B 開始呼叫sdf.parse()並順利i結束, 這樣 A 的 calendar記憶體儲的的date 變成了後來B設定的calendar的date
這個問題背後隱藏著一個更為重要的問題–無狀態:無狀態方法的好處之一,就是它在各種環境下,都可以安全的呼叫。衡量一個方法是否是有狀態的,就看它是否改動了其它的東西,比如全域性變數,比如例項的欄位。format方法在執行過程中改動了SimpleDateFormat的calendar欄位,所以,它是有狀態的。
這也同時提醒我們在開發和設計系統的時候注意下一下三點:
1.自己寫公用類的時候,要對多執行緒呼叫情況下的後果在註釋裡進行明確說明
2.對執行緒環境下,對每一個共享的可變變數都要注意其執行緒安全性
3.我們的類和方法在做設計的時候,要儘量設計成無狀態的
三.解決辦法
1.需要的時候建立新例項:
說明:在需要用到SimpleDateFormat 的地方新建一個例項,不管什麼時候,將有執行緒安全問題的物件由共享變為區域性私有都能避免多執行緒問題,不過也加重了建立物件的負擔。在一般情況下,這樣其實對效能影響比不是很明顯的。
2.使用同步:同步SimpleDateFormat物件
public class DateSyncUtil {
private static SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
public static String formatDate(Date date)throws ParseException{
synchronized(sdf){
return sdf.format(date);
}
}
public static Date parse(String strDate) throws ParseException{
synchronized(sdf){
return sdf.parse(strDate);
}
}
}
說明:當執行緒較多時,當一個執行緒呼叫該方法時,其他想要呼叫此方法的執行緒就要block,多執行緒併發量大的時候會對效能有一定的影響。
3.使用ThreadLocal:
public class ConcurrentDateUtil {
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
}
};
public static Date parse(String dateStr) throws ParseException {
return threadLocal.get().parse(dateStr);
}
public static String format(Date date) {
return threadLocal.get().format(date);
}
}
或
public class ThreadLocalDateUtil {
private static final String date_format = “yyyy-MM-dd HH:mm:ss”;
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();
public static DateFormat getDateFormat()
{
DateFormat df = threadLocal.get();
if(df==null){
df = new SimpleDateFormat(date_format);
threadLocal.set(df);
}
return df;
}
public static String formatDate(Date date) throws ParseException {
return getDateFormat().format(date);
}
public static Date parse(String strDate) throws ParseException {
return getDateFormat().parse(strDate);
}
}
說明:使用ThreadLocal, 也是將共享變數變為獨享,執行緒獨享肯定能比方法獨享在併發環境中能減少不少建立物件的開銷。如果對效能要求比較高的情況下,一般推薦使用這種方法。
4.拋棄JDK,使用其他類庫中的時間格式化類:
1.使用Apache commons 裡的FastDateFormat,宣稱是既快又執行緒安全的SimpleDateFormat, 可惜它只能對日期進行format, 不能對日期串進行解析。
2.使用Joda-Time類庫來處理時間相關問題
做一個簡單的壓力測試,方法一最慢,方法三最快,但是就算是最慢的方法一效能也不差,一般系統方法一和方法二就可以滿足,所以說在這個點很難成為你係統的瓶頸所在。從簡單的角度來說,建議使用方法一或者方法二,如果在必要的時候,追求那麼一點效能提升的話,可以考慮用方法三,用ThreadLocal做快取。
Joda-Time類庫對時間處理方式比較完美,建議使用。
———————————————————————-
Spring單例項、多執行緒安全、事務解析 引言:
在使用Spring時,很多人可能對Spring中為什麼DAO和Service物件採用單例項方式很迷惑,這些讀者是這麼認為的:
DAO物件必須包含一個數據庫的連線Connection,而這個Connection不是執行緒安全的,所以每個DAO都要包含一個不同的Connection物件例項,這樣一來DAO物件就不能是單例項的了。
上述觀點對了一半。對的是“每個DAO都要包含一個不同的Connection物件例項”這句話,錯的是“DAO物件就不能是單例項”。
其實Spring在實現Service和DAO物件時,使用了ThreadLocal這個類,這個是一切的核心! 如果你不知道什麼事ThreadLocal,請看 《 深入研究java.lang.ThreadLocal類》 :。請放心,這個類很簡單的。
要弄明白這一切,又得明白事務管理在Spring中是怎麼工作的,所以本文就對Spring中多執行緒、事務的問題進行解析。
Spring使用ThreadLocal解決執行緒安全問題:
Spring中DAO和Service都是以單例項的bean形式存在,Spring通過ThreadLocal類將有狀態的變數(例如資料庫連線Connection)本地執行緒化,從而做到多執行緒狀況下的安全。在一次請求響應的處理執行緒中, 該執行緒貫通展示、服務、資料持久化三層,通過ThreadLocal使得所有關聯的物件引用到的都是同一個變數。
參考下面程式碼,這個是《Spring3.x企業應用開發實戰中的例子》,本文後面也會多次用到該書中例子(有修改)。
public class SqlConnection {
//①使用ThreadLocal儲存Connection變數
privatestatic ThreadLocal <Connection>connThreadLocal = newThreadLocal<Connection>();
publicstatic Connection getConnection() {
// ②如果connThreadLocal沒有本執行緒對應的Connection建立一個新的Connection,
// 並將其儲存到執行緒本地變數中。
if (connThreadLocal.get() == null) {
Connection conn = getConnection();
connThreadLocal.set(conn);
return conn;
} else {
return connThreadLocal.get();
// ③直接返回執行緒本地變數
}
}
public voidaddTopic() {
// ④從ThreadLocal中獲取執行緒對應的Connection
try {
Statement stat = getConnection().createStatement();
} catch (SQLException e) {
e.printStackTrace();
}
}
} 這個是例子展示了不同執行緒使用TopicDao時如何使得每個執行緒都獲得不同的Connection例項副本,同時保持TopicDao本身是單例項。
事務管理器:
事務管理器用於管理各個事務方法,它產生一個事務管理上下文。下文以SpringJDBC的事務管理器DataSourceTransactionManager類為例子。
我們知道資料庫連線Connection在不同執行緒中是不能共享的,事務管理器為不同的事務執行緒利用ThreadLocal類提供獨立的Connection副本。事實上,它將Service和Dao中所有執行緒不安全的變數都提取出來單獨放在一個地方,並用ThreadLocal替換。而多執行緒可以共享的部分則以單例項方式存在。
事務傳播行為:
當我們呼叫Service的某個事務方法時,如果該方法內部又呼叫其它Service的事務方法,則會出現事務的巢狀。Spring定義了一套事務傳播行為,請參考。這裡我們假定都用的REQUIRED這個型別:如果當前沒有事務,就新建一個事務,如果已經存在一個事務,則加入到的當前事務。參考下面例子(程式碼不完整):
@Service( “userService”)
public class UserService extends BaseService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ScoreService scoreService;
public void logon(String userName) {
updateLastLogonTime(userName);
scoreService.addScore(userName, 20);
}
public void updateLastLogonTime(String userName) {
String sql = “UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?”;
jdbcTemplate.update(sql, System. currentTimeMillis(), userName);
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(“com/baobaotao/nestcall/applicatonContext.xml” );
UserService service = (UserService) ctx.getBean(“userService” );
service.logon( “tom”);
}
}
@Service( “scoreUserService” )
public class ScoreService extends BaseService{
@Autowired
private JdbcTemplate jdbcTemplate;
public void addScore(String userName, int toAdd) {
String sql = “UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?”;
jdbcTemplate.update(sql, toAdd, userName);
}
} 同時,在配置檔案中指定UserService、ScoreService中的所有方法都開啟事務。
上述例子中UserService.logon()執行開始時Spring建立一個新事務,UserService.updateLastLogonTime()和ScoreService.addScore()會加入這個事務中,好像所有的程式碼都“直接合並”了!
多執行緒中事務傳播的困惑:
還是上面那個例子,加入現在我在UserService.logon()方法中手動新開一個執行緒,然後在新開的執行緒中執行ScoreService.add()方法,此時事務傳播行為會怎麼樣?飛執行緒安全的變數,比如Connection會怎樣?改動之後的UserService 程式碼大體是:
@Service( “userService”)
public class UserService extends BaseService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ScoreService scoreService;
public void logon(String userName) {
updateLastLogonTime(userName);
Thread myThread = new MyThread(this.scoreService , userName, 20);//使用一個新執行緒執行
myThread .start();
}
public void updateLastLogonTime(String userName) {
String sql = “UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?”;
jdbcTemplate.update(sql, System. currentTimeMillis(), userName);
}
private class MyThread extends Thread {
private ScoreService scoreService;
private String userName;
private int toAdd;
private MyThread(ScoreService scoreService, String userName, int toAdd) {
this. scoreService = scoreService;
this. userName = userName;
this. toAdd = toAdd;
}
public void run() {
scoreService.addScore( userName, toAdd);
}
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(“com/baobaotao/multithread/applicatonContext.xml” );
UserService service = (UserService) ctx.getBean(“userService” );
service.logon( “tom”);
}
} 這個例子中,MyThread會新開一個事務,於是UserService.logon()和UserService.updateLastLogonTime()會在一個事務中,而ScoreService.addScore()在另一個事務中,需要注意的是這兩個事務都被事務管理器放在事務上下文中。
結論是:在事務屬性為REQUIRED時,在相同執行緒中進行相互巢狀呼叫的事務方法工作於相同的事務中。如果互相巢狀呼叫的事務方法工作在不同執行緒中,則不同執行緒下的事務方法工作在獨立的事務中。
底層資料庫連線Connection訪問問題
程式只要使用SpringDAO模板,例如JdbcTemplate進行資料訪問,一定沒有資料庫連線洩露問題!如果程式中顯式的獲取了資料連線Connection,則需要手工關閉它,否則就會洩露!
當Spring事務方法執行時,事務會放在事務上下文中,這個事務上下文在本事務執行執行緒中對同一個資料來源綁定了唯一一個數據連線,所有被該事務的上下文傳播的放發都共享這個資料連線。這一切都在Spring控制下,不會產生洩露。Spring提供了資料資源獲取工具類DataSourceUtils來獲取這個資料連線.
@Service( “jdbcUserService” )
public class JdbcUserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void logon(String userName) {
try {
Connection conn = jdbcTemplate.getDataSource().getConnection();
String sql = “UPDATE t_user SET last_logon_time=? WHERE user_name =?”;
jdbcTemplate.update(sql, System. currentTimeMillis(), userName);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void asynchrLogon(JdbcUserService userService, String userName) {
UserServiceRunner runner = new UserServiceRunner(userService, userName);
runner.start();
}
public static void reportConn(BasicDataSource basicDataSource) {
System. out.println( “連線數[active:idle]-[” +
basicDataSource.getNumActive()+”:” +basicDataSource.getNumIdle()+ “]”);
}
private static class UserServiceRunner extends Thread {
private JdbcUserService userService;
private String userName;
public UserServiceRunner(JdbcUserService userService, String userName) {
this. userService = userService;
this. userName = userName;
}
public void run() {
userService.logon( userName);
}
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(“com/baobaotao/connleak/applicatonContext.xml” );
JdbcUserService userService = (JdbcUserService) ctx.getBean(“jdbcUserService” );
JdbcUserService. asynchrLogon(userService, “tom”);
}
} 在這個例子中,main執行緒拿到一個UserService例項,獲取一個Connection的副本,它會被Spring管理,不會洩露。UserServiceRunner 執行緒手動從資料來源拿了一個Connection但沒有關閉因此會洩露。
如果希望使UserServiceRunner能拿到UserService中那個Connection們就要使用DataSourceUtils類,DataSourceUtils.getConnection()方法會首先檢視當前是否存在事務管理上下文,如果存在就嘗試從事務管理上下文拿連線,如果獲取失敗,直接從資料來源中拿。在獲取連線後,如果存在事務管理上下文則把連線繫結上去。
實際上,上面的程式碼只用改動一行,把login()方法中獲取連線那行改成就可以做到:
Connection conn = DataSourceUtils. getConnection ( jdbcTemplate .getDataSource());
需要注意的是:如果DataSourceUtils在沒有事務上下文的方法中使用getConnection()獲取連線,依然要手動管理這個連線!
此外,開啟了事務的方法要在整個事務方法結束後才釋放事務上下文繫結的Connection連線,而沒有開啟事務的方法在呼叫完Spring的Dao模板方法後立刻釋放。
多執行緒一定要與事務掛鉤麼?
不是!即便沒有開啟事務,利用ThreadLocal機制也能保證執行緒安全,Dao照樣可以操作資料。但是事務和多執行緒確實糾纏不清,上文已經分析了在多執行緒下事務傳播行為、事務對Connection獲取的影響。
結論:
•Spring中DAO和Service都是以單例項的bean形式存在,Spring通過ThreadLocal類將有狀態的變數(例如資料庫連線Connection)本地執行緒化,從而做到多執行緒狀況下的安全。在一次請求響應的處理執行緒中, 該執行緒貫通展示、服務、資料持久化三層,通過ThreadLocal使得所有關聯的物件引用到的都是同一個變數。
•在事務屬性為REQUIRED時,在相同執行緒中進行相互巢狀呼叫的事務方法工作於相同的事務中。如果互相巢狀呼叫的事務方法工作在不同執行緒中,則不同執行緒下的事務方法工作在獨立的事務中。
•程式只要使用SpringDAO模板,例如JdbcTemplate進行資料訪問,一定沒有資料庫連線洩露問題!如果程式中顯式的獲取了資料連線Connection,則需要手工關閉它,否則就會洩露!
•當Spring事務方法執行時,就產生一個事務上下文,它在本事務執行執行緒中對同一個資料來源綁定了一個唯一的資料連線,所有被該事務上下文傳播的方法都共享這個連線。要獲取這個連線,如要使用Spirng的資源獲取工具類DataSourceUtils。
•事務管理上下文就好比一個盒子,所有的事務都放在裡面。如果在某個事務方法中開啟一個新執行緒,新執行緒中執行另一個事務方法,則由上面第二條可知這兩個方法運行於兩個獨立的事務中,但是:如果使用DataSourcesUtils,則新執行緒中的方法可以從事務上下文中獲取原執行緒中的資料連線!
http://www.cnblogs.com/davidwang456/p/3832949.html
Spring的事務傳遞機制:
PROPAGATION_REQUIRED 如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。 PROPAGATION_SUPPORTS 支援當前事務,如果當前沒有事務,就以非事務方式執行。 PROPAGATION_MANDATORY 使用當前的事務,如果當前沒有事務,就丟擲異常。 PROPAGATION_REQUIRES_NEW 新建事務,如果當前存在事務,把當前事務掛起。 PROPAGATION_NOT_SUPPORTED 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。 PROPAGATION_NEVER 以非事務方式執行,如果當前存在事務,則丟擲異常。 PROPAGATION_NESTED 如果當前存在事務,則在巢狀事務內執行。如果當前沒有事務,則執行與 PROPAGATION_REQUIRED 類似的操作。
spring中管理的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是框架裡面封裝好的,已經被測試,不會出現執行緒同步問題了。所以出問題的地方就是我們自己系統裡面的業務物件,所以我們一定要注意這些業務物件裡面千萬不能要獨立成員變數,否則會出錯。
Spring中由容器託管的類如果沒有特殊宣告(scope = “prototype”),則預設為單列模式,當多使用者同時請求一個服務時,容器會給每一個請求分配一個執行緒,這是多個執行緒會併發執行該請求多對應的業務邏輯(成員方法),此時就要注意了,如果該處理邏輯中有對該單列狀態的修改(成員變數),則必須考慮執行緒同步問題;否則由於在業務邏輯中執行所需的區域性變數會分配在棧空間中,所以不需要同步。
其實函式本身是程式碼,程式碼是隻讀的,無論多少個執行緒同時調都無所謂(因為只是讀的嘛),但是函式中肯定是要用到資料的,如果資料是函式引數、區域性變數,那麼這些資料都是存在每個執行緒自己的棧上的,同時呼叫是沒有關係的,不會涉及到執行緒安全資源共享的問題。 但是如果使用到了全域性靜態變數或者類的成員變數的時候。就會出現資料安全的問題,還有,如果我們的成員變數在函式體內如果只進行讀操作,不進行寫操作,也是執行緒安全的。
簡而言之一句話,單例的方法在同一個時刻是可以被兩個執行緒同時呼叫的。我們在做程式的時候要儘可能少的使用類的成員變數,如果使用成員變數,儘量保證只對成員變數進行讀操作
當我們的很多使用者去修改自己的資訊的時候,使用者執行緒會通過呼叫dao(dao 我們都是給注入有配連結池的資料來源的),dao 會拿到連結池中的一個連結,將我們要處理的資訊(sql語句等)交付個數據庫,資料庫會按照自己的多執行緒處理機制完成執行緒的同步,然後進行資料安全處理,執行緒在完成資料處理後會將佔有的連結放回到連結池中。
——————————————————————————————————————
以下來自http://www.cnblogs.com/doit8791/p/4093808.html
Spring框架裡的bean,或者說元件,獲取例項的時候都是預設的單例模式,這是在多執行緒開發的時候要尤其注意的地方。
單例模式的意思就是隻有一個例項。單例模式確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項。這個類稱為單例類。
當多使用者同時請求一個服務時,容器會給每一個請求分配一個執行緒,這是多個執行緒會併發執行該請求多對應的業務邏輯(成員方法),此時就要注意了,如果該處理邏輯中有對該單列狀態的修改(體現為該單列的成員屬性),則必須考慮執行緒同步問題
同步機制的比較 ThreadLocal和執行緒同步機制相比有什麼優勢呢?ThreadLocal和執行緒同步機制都是為了解決多執行緒中相同變數的訪問衝突問題。
在同步機制中,通過物件的鎖機制保證同一時間只有一個執行緒訪問變數。這時該變數是多個執行緒共享的,使用同步機制要求程式慎密地分析什麼時候對變數進行讀寫,什麼時候需要鎖定某個物件,什麼時候釋放物件鎖等繁雜的問題,程式設計和編寫難度相對較大。
而ThreadLocal則從另一個角度來解決多執行緒的併發訪問。ThreadLocal會為每一個執行緒提供一個獨立的變數副本,從而隔離了多個執行緒對資料的訪問衝突。因為每一個執行緒都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。ThreadLocal提供了執行緒安全的共享物件,在編寫多執行緒程式碼時,可以把不安全的變數封裝進ThreadLocal。
由於ThreadLocal中可以持有任何型別的物件,低版本JDK所提供的get()返回的是Object物件,需要強制型別轉換。但JDK 5.0通過泛型很好的解決了這個問題,在一定程度地簡化ThreadLocal的使用
概括起來說,對於多執行緒資源共享的問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的方式。前者僅提供一份變數,讓不同的執行緒排隊訪問,而後者為每一個執行緒都提供了一份變數,因此可以同時訪問而互不影響。
Spring使用ThreadLocal解決執行緒安全問題
我們知道在一般情況下,只有無狀態的Bean才可以在多執行緒環境下共享,在Spring中,絕大部分Bean都可以宣告為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非執行緒安全狀態採用ThreadLocal進行處理,讓它們也成為執行緒安全的狀態,因為有狀態的Bean就可以在多執行緒中共享了。
一般的Web應用劃分為展現層、服務層和持久層三個層次,在不同的層中編寫對應的邏輯,下層通過介面向上層開放功能呼叫。在一般情況下,從接收請求到返回響應所經過的所有程式呼叫都同屬於一個執行緒
ThreadLocal是解決執行緒安全問題一個很好的思路,它通過為每個執行緒提供一個獨立的變數副本解決了變數併發訪問的衝突問題。在很多情況下,ThreadLocal比直接使用synchronized同步機制解決執行緒安全問題更簡單,更方便,且結果程式擁有更高的併發性。
如果你的程式碼所在的程序中有多個執行緒在同時執行,而這些執行緒可能會同時執行這段程式碼。如果每次執行結果和單執行緒執行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是執行緒安全的。 或者說:一個類或者程式所提供的介面對於執行緒來說是原子操作或者多個執行緒之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題。 執行緒安全問題都是由全域性變數及靜態變數引起的。
若每個執行緒中對全域性變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全域性變數是執行緒安全的;若有多個執行緒同時執行寫操作,一般都需要考慮執行緒同步,否則就可能影響執行緒安全。
1) 常量始終是執行緒安全的,因為只存在讀操作。
2)每次呼叫方法前都新建一個例項是執行緒安全的,因為不會訪問共享的資源。
3)區域性變數是執行緒安全的。因為每執行一個方法,都會在獨立的空間建立區域性變數,它不是共享的資源。區域性變數包括方法的引數變數和方法內變數。
有狀態就是有資料儲存功能。有狀態物件(Stateful Bean),就是有例項變數的物件 ,可以儲存資料,是非執行緒安全的。在不同方法呼叫間不保留任何狀態。
無狀態就是一次操作,不能儲存資料。無狀態物件(Stateless Bean),就是沒有例項變數的物件 .不能儲存資料,是不變類,是執行緒安全的。
有狀態物件:
無狀態的Bean適合用不變模式,技術就是單例模式,這樣可以共享例項,提高效能。有狀態的Bean,多執行緒環境下不安全,那麼適合用Prototype原型模式。Prototype: 每次對bean的請求都會建立一個新的bean例項。
Struts2預設的實現是Prototype模式。也就是每個請求都新生成一個Action例項,所以不存線上程安全問題。需要注意的是,如果由Spring管理action的生命週期, scope要配成prototype作用域。
二、執行緒安全案例:
SimpleDateFormat(下面簡稱sdf)類內部有一個Calendar物件引用,它用來儲存和這個sdf相關的日期資訊,例如sdf.parse(dateStr), sdf.format(date) 諸如此類的方法引數傳入的日期相關String, Date等等, 都是交友Calendar引用來儲存的.這樣就會導致一個問題,如果你的sdf是個static的, 那麼多個thread 之間就會共享這個sdf, 同時也是共享這個Calendar引用, 並且, 觀察 sdf.parse() 方法,你會發現有如下的呼叫:
Date parse() {
calendar.clear(); // 清理calendar
… // 執行一些操作, 設定 calendar 的日期什麼的
calendar.getTime(); // 獲取calendar的時間
}
這裡會導致的問題就是, 如果 執行緒A 呼叫了 sdf.parse(), 並且進行了 calendar.clear()後還未執行calendar.getTime()的時候,執行緒B又呼叫了sdf.parse(), 這時候執行緒B也執行了sdf.clear()方法, 這樣就導致執行緒A的的calendar資料被清空了(實際上A,B的同時被清空了). 又或者當 A 執行了calendar.clear() 後被掛起, 這時候B 開始呼叫sdf.parse()並順利i結束, 這樣 A 的 calendar記憶體儲的的date 變成了後來B設定的calendar的date
這個問題背後隱藏著一個更為重要的問題–無狀態:無狀態方法的好處之一,就是它在各種環境下,都可以安全的呼叫。衡量一個方法是否是有狀態的,就看它是否改動了其它的東西,比如全域性變數,比如例項的欄位。format方法在執行過程中改動了SimpleDateFormat的calendar欄位,所以,它是有狀態的。
這也同時提醒我們在開發和設計系統的時候注意下一下三點:
1.自己寫公用類的時候,要對多執行緒呼叫情況下的後果在註釋裡進行明確說明
2.對執行緒環境下,對每一個共享的可變變數都要注意其執行緒安全性
3.我們的類和方法在做設計的時候,要儘量設計成無狀態的
三.解決辦法
1.需要的時候建立新例項:
說明:在需要用到SimpleDateFormat 的地方新建一個例項,不管什麼時候,將有執行緒安全問題的物件由共享變為區域性私有都能避免多執行緒問題,不過也加重了建立物件的負擔。在一般情況下,這樣其實對效能影響比不是很明顯的。
2.使用同步:同步SimpleDateFormat物件
public class DateSyncUtil {
private static SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
public static String formatDate(Date date)throws ParseException{
synchronized(sdf){
return sdf.format(date);
}
}
public static Date parse(String strDate) throws ParseException{
synchronized(sdf){
return sdf.parse(strDate);
}
}
}
說明:當執行緒較多時,當一個執行緒呼叫該方法時,其他想要呼叫此方法的執行緒就要block,多執行緒併發量大的時候會對效能有一定的影響。
3.使用ThreadLocal:
public class ConcurrentDateUtil {
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
}
};
public static Date parse(String dateStr) throws ParseException {
return threadLocal.get().parse(dateStr);
}
public static String format(Date date) {
return threadLocal.get().format(date);
}
}
或
public class ThreadLocalDateUtil {
private static final String date_format = “yyyy-MM-dd HH:mm:ss”;
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();
public static DateFormat getDateFormat()
{
DateFormat df = threadLocal.get();
if(df==null){
df = new SimpleDateFormat(date_format);
threadLocal.set(df);
}
return df;
}
public static String formatDate(Date date) throws ParseException {
return getDateFormat().format(date);
}
public static Date parse(String strDate) throws ParseException {
return getDateFormat().parse(strDate);
}
}
說明:使用ThreadLocal, 也是將共享變數變為獨享,執行緒獨享肯定能比方法獨享在併發環境中能減少不少建立物件的開銷。如果對效能要求比較高的情況下,一般推薦使用這種方法。
4.拋棄JDK,使用其他類庫中的時間格式化類:
1.使用Apache commons 裡的FastDateFormat,宣稱是既快又執行緒安全的SimpleDateFormat, 可惜它只能對日期進行format, 不能對日期串進行解析。
2.使用Joda-Time類庫來處理時間相關問題
做一個簡單的壓力測試,方法一最慢,方法三最快,但是就算是最慢的方法一效能也不差,一般系統方法一和方法二就可以滿足,所以說在這個點很難成為你係統的瓶頸所在。從簡單的角度來說,建議使用方法一或者方法二,如果在必要的時候,追求那麼一點效能提升的話,可以考慮用方法三,用ThreadLocal做快取。
Joda-Time類庫對時間處理方式比較完美,建議使用。
———————————————————————-
Spring單例項、多執行緒安全、事務解析
在使用Spring時,很多人可能對Spring中為什麼DAO和Service物件採用單例項方式很迷惑,這些讀者是這麼認為的:
DAO物件必須包含一個數據庫的連線Connection,而這個Connection不是執行緒安全的,所以每個DAO都要包含一個不同的Connection物件例項,這樣一來DAO物件就不能是單例項的了。
上述觀點對了一半。對的是“每個DAO都要包含一個不同的Connection物件例項”這句話,錯的是“DAO物件就不能是單例項”。
其實Spring在實現Service和DAO物件時,使用了ThreadLocal這個類,這個是一切的核心! 如果你不知道什麼事ThreadLocal,請看 《 深入研究java.lang.ThreadLocal類》 :。請放心,這個類很簡單的。
要弄明白這一切,又得明白事務管理在Spring中是怎麼工作的,所以本文就對Spring中多執行緒、事務的問題進行解析。
Spring使用ThreadLocal解決執行緒安全問題:
Spring中DAO和Service都是以單例項的bean形式存在,Spring通過ThreadLocal類將有狀態的變數(例如資料庫連線Connection)本地執行緒化,從而做到多執行緒狀況下的安全。在一次請求響應的處理執行緒中, 該執行緒貫通展示、服務、資料持久化三層,通過ThreadLocal使得所有關聯的物件引用到的都是同一個變數。
參考下面程式碼,這個是《Spring3.x企業應用開發實戰中的例子》,本文後面也會多次用到該書中例子(有修改)。
public class SqlConnection {
//①使用ThreadLocal儲存Connection變數
privatestatic ThreadLocal <Connection>connThreadLocal = newThreadLocal<Connection>();
publicstatic Connection getConnection() {
// ②如果connThreadLocal沒有本執行緒對應的Connection建立一個新的Connection,
// 並將其儲存到執行緒本地變數中。
if (connThreadLocal.get() == null) {
Connection conn = getConnection();
connThreadLocal.set(conn);
return conn;
} else {
return connThreadLocal.get();
// ③直接返回執行緒本地變數
}
}
public voidaddTopic() {
// ④從ThreadLocal中獲取執行緒對應的Connection
try {
Statement stat = getConnection().createStatement();
} catch (SQLException e) {
e.printStackTrace();
}
}
} 這個是例子展示了不同執行緒使用TopicDao時如何使得每個執行緒都獲得不同的Connection例項副本,同時保持TopicDao本身是單例項。
事務管理器:
事務管理器用於管理各個事務方法,它產生一個事務管理上下文。下文以SpringJDBC的事務管理器DataSourceTransactionManager類為例子。
我們知道資料庫連線Connection在不同執行緒中是不能共享的,事務管理器為不同的事務執行緒利用ThreadLocal類提供獨立的Connection副本。事實上,它將Service和Dao中所有執行緒不安全的變數都提取出來單獨放在一個地方,並用ThreadLocal替換。而多執行緒可以共享的部分則以單例項方式存在。
事務傳播行為:
當我們呼叫Service的某個事務方法時,如果該方法內部又呼叫其它Service的事務方法,則會出現事務的巢狀。Spring定義了一套事務傳播行為,請參考。這裡我們假定都用的REQUIRED這個型別:如果當前沒有事務,就新建一個事務,如果已經存在一個事務,則加入到的當前事務。參考下面例子(程式碼不完整):
@Service( “userService”)
public class UserService extends BaseService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ScoreService scoreService;
public void logon(String userName) {
updateLastLogonTime(userName);
scoreService.addScore(userName, 20);
}
public void updateLastLogonTime(String userName) {
String sql = “UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?”;
jdbcTemplate.update(sql, System. currentTimeMillis(), userName);
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(“com/baobaotao/nestcall/applicatonContext.xml” );
UserService service = (UserService) ctx.getBean(“userService” );
service.logon( “tom”);
}
}
@Service( “scoreUserService” )
public class ScoreService extends BaseService{
@Autowired
private JdbcTemplate jdbcTemplate;
public void addScore(String userName, int toAdd) {
String sql = “UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?”;
jdbcTemplate.update(sql, toAdd, userName);
}
} 同時,在配置檔案中指定UserService、ScoreService中的所有方法都開啟事務。
上述例子中UserService.logon()執行開始時Spring建立一個新事務,UserService.updateLastLogonTime()和ScoreService.addScore()會加入這個事務中,好像所有的程式碼都“直接合並”了!
多執行緒中事務傳播的困惑:
還是上面那個例子,加入現在我在UserService.logon()方法中手動新開一個執行緒,然後在新開的執行緒中執行ScoreService.add()方法,此時事務傳播行為會怎麼樣?飛執行緒安全的變數,比如Connection會怎樣?改動之後的UserService 程式碼大體是:
@Service( “userService”)
public class UserService extends BaseService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ScoreService scoreService;
public void logon(String userName) {
updateLastLogonTime(userName);
Thread myThread = new MyThread(this.scoreService , userName, 20);//使用一個新執行緒執行
myThread .start();
}
public void updateLastLogonTime(String userName) {
String sql = “UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?”;
jdbcTemplate.update(sql, System. currentTimeMillis(), userName);
}
private class MyThread extends Thread {
private ScoreService scoreService;
private String userName;
private int toAdd;
private MyThread(ScoreService scoreService, String userName, int toAdd) {
this. scoreService = scoreService;
this. userName = userName;
this. toAdd = toAdd;
}
public void run() {
scoreService.addScore( userName, toAdd);
}
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(“com/baobaotao/multithread/applicatonContext.xml” );
UserService service = (UserService) ctx.getBean(“userService” );
service.logon( “tom”);
}
} 這個例子中,MyThread會新開一個事務,於是UserService.logon()和UserService.updateLastLogonTime()會在一個事務中,而ScoreService.addScore()在另一個事務中,需要注意的是這兩個事務都被事務管理器放在事務上下文中。
結論是:在事務屬性為REQUIRED時,在相同執行緒中進行相互巢狀呼叫的事務方法工作於相同的事務中。如果互相巢狀呼叫的事務方法工作在不同執行緒中,則不同執行緒下的事務方法工作在獨立的事務中。
底層資料庫連線Connection訪問問題
程式只要使用SpringDAO模板,例如JdbcTemplate進行資料訪問,一定沒有資料庫連線洩露問題!如果程式中顯式的獲取了資料連線Connection,則需要手工關閉它,否則就會洩露!
當Spring事務方法執行時,事務會放在事務上下文中,這個事務上下文在本事務執行執行緒中對同一個資料來源綁定了唯一一個數據連線,所有被該事務的上下文傳播的放發都共享這個資料連線。這一切都在Spring控制下,不會產生洩露。Spring提供了資料資源獲取工具類DataSourceUtils來獲取這個資料連線.
@Service( “jdbcUserService” )
public class JdbcUserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void logon(String userName) {
try {
Connection conn = jdbcTemplate.getDataSource().getConnection();
String sql = “UPDATE t_user SET last_logon_time=? WHERE user_name =?”;
jdbcTemplate.update(sql, System. currentTimeMillis(), userName);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void asynchrLogon(JdbcUserService userService, String userName) {
UserServiceRunner runner = new UserServiceRunner(userService, userName);
runner.start();
}
public static void reportConn(BasicDataSource basicDataSource) {
System. out.println( “連線數[active:idle]-[” +
basicDataSource.getNumActive()+”:” +basicDataSource.getNumIdle()+ “]”);
}
private static class UserServiceRunner extends Thread {
private JdbcUserService userService;
private String userName;
public UserServiceRunner(JdbcUserService userService, String userName) {
this. userService = userService;
this. userName = userName;
}
public void run() {
userService.logon( userName);
}
}
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(“com/baobaotao/connleak/applicatonContext.xml” );
JdbcUserService userService = (JdbcUserService) ctx.getBean(“jdbcUserService” );
JdbcUserService. asynchrLogon(userService, “tom”);
}
} 在這個例子中,main執行緒拿到一個UserService例項,獲取一個Connection的副本,它會被Spring管理,不會洩露。UserServiceRunner 執行緒手動從資料來源拿了一個Connection但沒有關閉因此會洩露。
如果希望使UserServiceRunner能拿到UserService中那個Connection們就要使用DataSourceUtils類,DataSourceUtils.getConnection()方法會首先檢視當前是否存在事務管理上下文,如果存在就嘗試從事務管理上下文拿連線,如果獲取失敗,直接從資料來源中拿。在獲取連線後,如果存在事務管理上下文則把連線繫結上去。
實際上,上面的程式碼只用改動一行,把login()方法中獲取連線那行改成就可以做到:
Connection conn = DataSourceUtils. getConnection ( jdbcTemplate .getDataSource());
需要注意的是:如果DataSourceUtils在沒有事務上下文的方法中使用getConnection()獲取連線,依然要手動管理這個連線!
此外,開啟了事務的方法要在整個事務方法結束後才釋放事務上下文繫結的Connection連線,而沒有開啟事務的方法在呼叫完Spring的Dao模板方法後立刻釋放。
多執行緒一定要與事務掛鉤麼?
不是!即便沒有開啟事務,利用ThreadLocal機制也能保證執行緒安全,Dao照樣可以操作資料。但是事務和多執行緒確實糾纏不清,上文已經分析了在多執行緒下事務傳播行為、事務對Connection獲取的影響。
結論:
•Spring中DAO和Service都是以單例項的bean形式存在,Spring通過ThreadLocal類將有狀態的變數(例如資料庫連線Connection)本地執行緒化,從而做到多執行緒狀況下的安全。在一次請求響應的處理執行緒中, 該執行緒貫通展示、服務、資料持久化三層,通過ThreadLocal使得所有關聯的物件引用到的都是同一個變數。
•在事務屬性為REQUIRED時,在相同執行緒中進行相互巢狀呼叫的事務方法工作於相同的事務中。如果互相巢狀呼叫的事務方法工作在不同執行緒中,則不同執行緒下的事務方法工作在獨立的事務中。
•程式只要使用SpringDAO模板,例如JdbcTemplate進行資料訪問,一定沒有資料庫連線洩露問題!如果程式中顯式的獲取了資料連線Connection,則需要手工關閉它,否則就會洩露!
•當Spring事務方法執行時,就產生一個事務上下文,它在本事務執行執行緒中對同一個資料來源綁定了一個唯一的資料連線,所有被該事務上下文傳播的方法都共享這個連線。要獲取這個連線,如要使用Spirng的資源獲取工具類DataSourceUtils。
•事務管理上下文就好比一個盒子,所有的事務都放在裡面。如果在某個事務方法中開啟一個新執行緒,新執行緒中執行另一個事務方法,則由上面第二條可知這兩個方法運行於兩個獨立的事務中,但是:如果使用DataSourcesUtils,則新執行緒中的方法可以從事務上下文中獲取原執行緒中的資料連線!