1. 程式人生 > >Spring的事務管理多執行緒的困惑

Spring的事務管理多執行緒的困惑

由於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。 

啟動獨立執行緒呼叫事務方法

Java程式碼  收藏程式碼
  1. package com.baobaotao.multithread;  
  2. import org.springframework.beans.factory.annotation.Autowired;  
  3. import org.springframework.jdbc.core.JdbcTemplate;  
  4. import org.springframework.context.ApplicationContext;  
  5. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  6. import org.springframework.stereotype.Service;  
  7. import org.apache.commons.dbcp.BasicDataSource;  
  8. @Service("userService")  
  9. public class UserService extends BaseService {  
  10.     @Autowired  
  11.     private JdbcTemplate jdbcTemplate;  
  12.     @Autowired  
  13.     private ScoreService scoreService;  
  14.     public void logon(String userName) {  
  15.         System.out.println("before userService.updateLastLogonTime method...");  
  16.         updateLastLogonTime(userName);  
  17.         System.out.println("after userService.updateLastLogonTime method...");  
  18.         //scoreService.addScore(userName, 20);//①在同一執行緒中呼叫scoreService#addScore()  
  19.         //②在一個新執行緒中執行scoreService#addScore()  
  20.         Thread myThread = new MyThread(this.scoreService, userName, 20);//使用一個新執行緒執行  
  21.         myThread.start();  
  22.     }  
  23.     public void updateLastLogonTime(String userName) {  
  24.         String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";  
  25.         jdbcTemplate.update(sql, System.currentTimeMillis(), userName);  
  26.     }  
  27.     //③負責執行scoreService#addScore()的執行緒類  
  28.     private class MyThread extends Thread {  
  29.         private ScoreService scoreService;  
  30.         private String userName;  
  31.         private int toAdd;  
  32.         private MyThread(ScoreService scoreService, String userName, int toAdd) {  
  33.             this.scoreService = scoreService;  
  34.             this.userName = userName;  
  35.             this.toAdd = toAdd;  
  36.         }  
  37.         public void run() {  
  38.             try {  
  39.                 Thread.sleep(2000);  
  40.             } catch (InterruptedException e) {  
  41.                 e.printStackTrace();  
  42.             }  
  43.             System.out.println("before scoreService.addScor method...");  
  44.             scoreService.addScore(userName, toAdd);  
  45.             System.out.println("after scoreService.addScor method...");  
  46.         }  
  47.     }  
  48. }  

   將日誌級別設定為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, [email protected], 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, [email protected], MySQL-AB JDBC Driver] 
Releasing JDBC Connection [jdbc:mysql://localhost:3306/sampledb, [email protected], 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, [email protected], 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, [email protected], MySQL-AB JDBC Driver] 
Releasing JDBC Connection [jdbc:mysql://localhost:3306/sampledb, [email protected], MySQL-AB JDBC Driver] after transaction 
Returning JDBC Connection to DataSource 
after scoreService.addScor method...

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