Spring的事务管理难点剖析(4):多线程的困惑(一)

2014-11-24 08:46:58 · 作者: · 浏览: 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。


启动独立线程调用事务方法

01 package com.baobaotao.multithread;

02

03 import org.springframework.beans.factory.annotation.Autowired;

04 import org.springframework.jdbc.core.JdbcTemplate;

05 import org.springframework.context.ApplicationContext;

06 import org.springframework.context.support.ClassPathXmlApplicationContext;

07 import org.springframework.stereotype.Service;

08 import org.apache.commons.dbcp.BasicDataSource;

09

10 @Service("userService")

11 public class UserService extends BaseService {

12 @Autowired

13 private JdbcTemplate jdbcTemplate;

14

15 @Autowired

16 private ScoreService scoreService;

17

18 public void logon(String userName) {

19 System.out.println("before userService.updateLastLogonTime method...");

20 updateLastLogonTime(userName);

21 System.out.println("after userService.updateLastLogonTime method...");

22

23 //scoreService.addScore(userName, 20);//①在同一线程中调用scoreService#addScore()

24

25 //②在一个新线程中执行scoreService#addScore()

26 Thread myThread = new MyThread(this.scoreService, userName, 20);//使用一个新线程运行

27 myThread.start();

28 }

29

30 public void updateLastLogonTime(String userName) {

31 String sql = "UPDATE t_user u SET u.last_logon_time = WHERE user_name = ";

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

33 }

34

35 //③负责执行scoreService#addScore()的线程类

36 private class MyThread extends Thread {

37 private ScoreService scoreService;

38 private String userName;

39 private int toAdd;

40 private MyThread(ScoreService scoreService, String userName, int toAdd) {

41 this.scoreService = scoreService;

42 this.userName = userName;

43 this.toAdd = toAdd;

44 }

45

46 public void run() {

47 try {

48 Thread.sleep(2000);

49 } catch (InterruptedException e) {

50 e.printStackTrace();

51 }

52 System.out.println("before scoreService.addScor method...");

53 scoreService.addScore(userName, toAdd);

54 System.out.println("after scoreService.addScor method...");

55 }

56 }

57 }

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

引用
before userService.l