您的位置:首页 > 编程语言 > Java开发

Spring事务管理高级应用难点剖析之多线程的困惑

2016-01-05 14:17 429 查看
由于Spring事务管理器是通过线程相关的ThreadLocal来保存数据访问基础设施,再结合IOC和AOP实现高级声明式事务的功能,所以Spring的事务天然地和线程有着千丝万缕的联系。

  我们知道Web容器本身就是多线程的,Web容器为一个Http请求创建一个独立的线程,所以由此请求所牵涉到的Spring容器中的Bean也是运行于多线程的环境下。在绝大多数情况下,Spring的Bean都是单实例的(singleton),单实例Bean的最大的好处是线程无关性,不存在多线程并发访问的问题,也即是线程安全的。一个类能够以单实例的方式运行的前提是“无状态”:即一个类不能拥有状态化的成员变量。我们知道,在传统的编程中,DAO必须执有一个Connection,而Connection即是状态化的对象。所以传统的DAO不能做成单实例的,每次要用时都必须new一个新的实例。传统的Service由于将有状态的DAO作为成员变量,所以传统的Service本身也是有状态的。

  但是在Spring中,DAO和Service都以单实例的方式存在。Spring是通过ThreadLocal将有状态的变量(如Connection等)本地线程化,达到另一个层面上的“线程无关”,从而实现线程安全。Spring不遗余力地将状态化的对象无状态化,就是要达到单实例化Bean的目的。由于Spring已经通过ThreadLocal的设施将Bean无状态化,所以Spring中单实例Bean对线程安全问题拥有了一种天生的免疫能力。不但单实例的Service可以成功运行于多线程环境中,Service本身还可以自由地启动独立线程以执行其它的Service。下面,通过一个实例对此进行描述:

  清单13UserService.java在事务方法中启动独立线程运行另一个事务方法

 

 

 40.@Service("userService")

  41.publicclassUserServiceextendsBaseService{

  42.@Autowired

  43.privateJdbcTemplatejdbcTemplate;

  44.

  45.@Autowired

  46.privateScoreServicescoreService;

  47.//①在logon方法体中启动一个独立的线程,在该独立的线程中执行ScoreService#addScore()方法

  48.publicvoidlogon(StringuserName){

  49.System.out.println("logonmethod...");

  50.updateLastLogonTime(userName);

  51.ThreadmyThread=newMyThread(this.scoreService,userName,20);

  52.myThread.start();

  53.}

  54.

  55.publicvoidupdateLastLogonTime(StringuserName){

  56.System.out.println("updateLastLogonTime...");

  57.Stringsql="UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?";

  58.jdbcTemplate.update(sql,System.currentTimeMillis(),userName);

  59.}

  60.//②封装ScoreService#addScore()的线程

  61.privateclassMyThreadextendsThread{

  62.privateScoreServicescoreService;

  63.privateStringuserName;

  64.privateinttoAdd;

  65.privateMyThread(ScoreServicescoreService,StringuserName,inttoAdd){

  66.this.scoreService=scoreService;

  67.this.userName=userName;

  68.this.toAdd=toAdd;

  69.}

  70.publicvoidrun(){

  71.scoreService.addScore(userName,toAdd);

  72.}

  73.}

  74.}

 

        将日志级别设置为DEBUG,执行UserService#logon()方法,观察以下输出的日志:

  清单14执行日志

 

 

 75.[main](AbstractPlatformTransactionManager.java:365)-Creatingnewtransactionwithname

  76.[user.multithread.UserService.logon]:PROPAGATION_REQUIRED,ISOLATION_DEFAULT①

  77.

  78.[main](DataSourceTransactionManager.java:205)-AcquiredConnection

  79.[org.apache.commons.dbcp.PoolableConnection@1353249]forJDBCtransaction

  80.

  81.logonmethod...

  82.

  83.updateLastLogonTime...

  84.

  85.[main](JdbcTemplate.java:785)-ExecutingpreparedSQLupdate

  86.[main](JdbcTemplate.java:569)-ExecutingpreparedSQLstatement

  87.[UPDATEt_useruSETu.last_logon_time=?WHEREuser_name=?]

  88.[main](JdbcTemplate.java:794)-SQLupdateaffected0rows

  89.[main](AbstractPlatformTransactionManager.java:752)-Initiatingtransactioncommit

  90.

  91.[Thread-2](AbstractPlatformTransactionManager.java:365)-

  92.Creatingnewtransactionwithname[user.multithread.ScoreService.addScore]:

  93.PROPAGATION_REQUIRED,ISOLATION_DEFAULT②

  94.[main](DataSourceTransactionManager.java:265)-CommittingJDBCtransaction

  95.onConnection[org.apache.commons.dbcp.PoolableConnection@1353249]③

  96.

  97.[main](DataSourceTransactionManager.java:323)-ReleasingJDBCConnection

  98.[org.apache.commons.dbcp.PoolableConnection@1353249]aftertransaction

  99.[main](DataSourceUtils.java:312)-ReturningJDBCConnectiontoDataSource

  100.

  101.[Thread-2](DataSourceTransactionManager.java:205)-AcquiredConnection

  102.[org.apache.commons.dbcp.PoolableConnection@10dc656]forJDBCtransaction

  103.

  104.addScore...

  105.

  106.[main](JdbcTemplate.java:416)-ExecutingSQLstatement

  107.[DELETEFROMt_userWHEREuser_name='tom']

  108.[main](DataSourceUtils.java:112)-FetchingJDBCConnectionfromDataSource

  109.[Thread-2](JdbcTemplate.java:785)-ExecutingpreparedSQLupdate

  110.[Thread-2](JdbcTemplate.java:569)-ExecutingpreparedSQLstatement

  111.[UPDATEt_useruSETu.score=u.score+?WHEREuser_name=?]

  112.[main](DataSourceUtils.java:312)-ReturningJDBCConnectiontoDataSource

  113.[Thread-2](JdbcTemplate.java:794)-SQLupdateaffected0rows

  114.[Thread-2](AbstractPlatformTransactionManager.java:752)-Initiatingtransactioncommit

  115.[Thread-2](DataSourceTransactionManager.java:265)-CommittingJDBCtransaction

  116.onConnection[org.apache.commons.dbcp.PoolableConnection@10dc656]④

  117.[Thread-2](DataSourceTransactionManager.java:323)-ReleasingJDBCConnection

  118.[org.apache.commons.dbcp.PoolableConnection@10dc656]aftertransaction

 

  在①处,在主线程(main)执行的UserService#logon()方法的事务启动,在③处,其对应的事务提交,而在子线程(Thread-2)执行的ScoreService#addScore()方法的事务在②处启动,在④处对应的事务提交。

  所以,我们可以得出这样的结论:在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果这些相互嵌套调用的方法工作在不同的线程中,不同线程下的事务方法工作在独立的事务中。

  小结

  Spring声明式事务是Spring最核心,最常用的功能。由于Spring通过IOC和AOP的功能非常透明地实现了声明式事务的功能,一般的开发者基本上无须了解Spring声明式事务的内部细节,仅需要懂得如何配置就可以了。

  但是在实际应用开发过程中,Spring的这种透明的高阶封装在带来便利的同时,也给我们带来了迷惑。就像通过流言传播的消息,最终听众已经不清楚事情的真相了,而这对于应用开发来说是很危险的。本系列文章通过剖析实际应用中给开发者造成迷惑的各种难点,通过分析Spring事务管理的内部运作机制将真相还原出来。在本文中,我们通过剖析了解到以下的真相:

  ◆在没有事务管理的情况下,DAO照样可以顺利进行数据操作;

  ◆将应用分成Web,Service及DAO层只是一种参考的开发模式,并非是事务管理工作的前提条件;

  ◆Spring通过事务传播机制可以很好地应对事务方法嵌套调用的情况,开发者无须为了事务管理而刻意改变服务方法的设计;

  ◆由于单实例的对象不存在线程安全问题,所以进行事务管理增强的Bean可以很好地工作在多线程环境下。

  ◆混合使用多种数据访问技术(如SpringJDBC+Hibernate)的事务管理问题;

  ◆在通过Bean的方法通过SpringAOP增强存在哪些特殊的情况。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  事务 多线程