我們知道Web容器本身就是多線程的,Web容器爲一個HTTP請求創建一個獨立的線程(實際上大多數Web容器採用共享線程池),所以由此請求所牽涉到 的Spring容器中的Bean也是運行於多線程的環境下。在絕大多數情況下,Spring的Bean都是單實例的(singleton),單實例 Bean的最大好處是線程無關性,不存在多線程併發訪問的問題,也就是線程安全的。
一個類能夠以單實例的方式運行的前提是“無狀態”:即一個類不能擁有狀態化的成員變量。我們知道,在傳統的編程中,DAO必須持有一個 Connection,而Connection即是狀態化的對象。所以傳統的DAO不能做成單實例的,每次要用時都必須創建一個新的實例。傳統的 Service由於內部包含了若干個有狀態的DAO成員變量,所以其本身也是有狀態的。
但是在Spring中,DAO和Service都以單實例的方式存在。Spring是通過ThreadLocal將有狀態的變量(如Connection 等)本地線程化,達到另一個層面上的“線程無關”,從而實現線程安全。Spring不遺餘力地將有狀態的對象無狀態化,就是要達到單實例化Bean的目 的。
由於Spring已經通過ThreadLocal的設施將Bean無狀態化,所以Spring中單實例Bean對線程安全問題擁有了一種天生的免疫能力。 不但單實例的Service可以成功運行於多線程環境中,Service本身還可以自由地啓動獨立線程以執行其他的Service。
啓動獨立線程調用事務方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
package
com.baobaotao.multithread; import
org.springframework.beans.factory.annotation.Autowired; import
org.springframework.jdbc.core.JdbcTemplate; import
org.springframework.context.ApplicationContext; import
org.springframework.context.support.ClassPathXmlApplicationContext; import
org.springframework.stereotype.Service; import
org.apache.commons.dbcp.BasicDataSource; @Service ( "userService" )
public
class
UserService extends
BaseService { @Autowired private
JdbcTemplate jdbcTemplate; @Autowired private
ScoreService scoreService; public
void
logon(String userName) { System.out.println( "before
userService.updateLastLogonTime method..." );
updateLastLogonTime(userName);
System.out.println( "after
userService.updateLastLogonTime method..." );
//scoreService.addScore(userName,
20);//①在同一線程中調用scoreService#addScore() //②在一個新線程中執行scoreService#addScore()
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); }
//③負責執行scoreService#addScore()的線程類
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() { try
{ Thread.sleep( 2000 );
}
catch
(InterruptedException e) { e.printStackTrace();
}
System.out.println( "before
scoreService.addScor method..." );
scoreService.addScore(userName,
toAdd); System.out.println( "after
scoreService.addScor method..." );
}
}
} |
將日誌級別設置爲DEBUG,執行UserService#logon()方法,觀察以下輸出日誌:
引用
before userService.logon method...
//①創建一個事務
Creating new transaction with name [com.baobaotao.multithread.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] for JDBC transaction
…
SQL update affected 1 rows
after userService.updateLastLogonTime method...
Initiating transaction commit
//②提交①處開啓的事務
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver]
Releasing JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] after transaction
…
Returning JDBC Connection to DataSource
before scoreService.addScor method...
//③創建一個事務
Creating new transaction with name [com.baobaotao.multithread.ScoreService.addScore]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] for JDBC transaction
…
SQL update affected 0 rows
Initiating transaction commit
//④提交③處開啓的事務
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver]
Releasing JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] after transaction
Returning JDBC Connection to DataSource
after scoreService.addScor method...
//①創建一個事務
Creating new transaction with name [com.baobaotao.multithread.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] for JDBC transaction
…
SQL update affected 1 rows
after userService.updateLastLogonTime method...
Initiating transaction commit
//②提交①處開啓的事務
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver]
Releasing JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] after transaction
…
Returning JDBC Connection to DataSource
before scoreService.addScor method...
//③創建一個事務
Creating new transaction with name [com.baobaotao.multithread.ScoreService.addScore]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
Acquired Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] for JDBC transaction
…
SQL update affected 0 rows
Initiating transaction commit
//④提交③處開啓的事務
Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver]
Releasing JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] after transaction
Returning JDBC Connection to DataSource
after scoreService.addScor method...
在①處,在主線程(main)執行的UserService#logon()方法的事務啓動,在②處,其對應的事務提交。而在子線程(Thread-2)執行的ScoreService#addScore()方法的事務在③處啓動,在④處對應的事務提交。
所以,我們可以得出這樣的結論:在相同線程中進行相互嵌套調用的事務方法工作於相同的事務中。如果這些相互嵌套調用的方法工作在不同的線程中,則不同線程下的事務方法工作在獨立的事務中。
注:以上內容摘自《Spring 3.x企業應用開發實戰》