Spring的事務管理難點剖析(4):多線程的困惑


   因爲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

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()方法,觀察如下輸出日誌:mysql

引用
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企業應用開發實戰》
相關文章
相關標籤/搜索