Spring通過單實例化Bean簡化多線程問題

   由於Spring的事務管理器是通過線程相關的ThreadLocal來保存數據訪問基礎設施(也即Connection實例),再結合IoC和AOP實現高級聲明式事務的功能,所以Spring的事務天然地和線程有着千絲萬縷的聯繫。 
   我們知道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...


   在①處,在主線程(main)執行的UserService#logon()方法的事務啓動,在②處,其對應的事務提交。而在子線程(Thread-2)執行的ScoreService#addScore()方法的事務在③處啓動,在④處對應的事務提交。 
   所以,我們可以得出這樣的結論:在相同線程中進行相互嵌套調用的事務方法工作於相同的事務中。如果這些相互嵌套調用的方法工作在不同的線程中,則不同線程下的事務方法工作在獨立的事務中。 

  注:以上內容摘自《Spring 3.x企業應用開發實戰》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章