ThreadLocal与Spring 事务管理

Posted java达人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal与Spring 事务管理相关的知识,希望对你有一定的参考价值。


编写线程安全代码的关键是管理程序中的共享可变状态,除了通过synchronized加锁机制防止多个线程同时访问同一段数据外,还有一种方法就是通过ThreadLocal消除数据的共享,ThreadLocal会为各自线程创建相应的变量副本(线程局部变量),每个副本都由各自线程管理,这样就避免了对共享资源的访问冲突,也减少了同步时的性能消耗。我们来看一段示例:

 

class Sequence implements Runnable {

 private final int tid;

 

 public Sequence(int tid) {

  this.tid = tid;

 }

 

 public void run() {

  while (!Thread.currentThread().isInterrupted()&&VarHolder.get()<6) {

   VarHolder.increment();

   System.out.println(this);

//提示调度器,让相同优先级的线程获得运行的机会,方便重现竞争条件的情景   

   Thread.yield();

  }

 }

 

 public String toString() {

  return "tid" + tid + ": " + VarHolder.get();

 }

}

 




public class VarHolder {

 private static ThreadLocal<Integer> value = new ThreadLocal<Integer>() {

 

  protected synchronized Integer initialValue() {

   return 0;

  }

 };

 

 public static void increment() {

  // set()设置当前线程的局部线程变量的值

  value.set(value.get() + 1);

 }

 

 public static int get() {

  //get()返回当前线程对应的线程局部变量

  return value.get();

 }

 

 public static void main(String[] args) throws Exception {

  ExecutorService exec = Executors.newCachedThreadPool();

  for (int i = 0; i < 5; i++){

   exec.execute(new Sequence(i));

  }

  Thread.sleep(2000);

  exec.shutdownNow();

 }

}

 

运行结果:

tid3: 1

tid0: 1

tid1: 1

tid4: 1

tid2: 1

tid3: 2

tid2: 2

tid3: 3

tid4: 2

tid3: 4

tid4: 3

tid1: 2

tid0: 2

tid3: 5

tid2: 3

tid1: 3

tid0: 3

tid4: 4

tid0: 4

tid1: 4

tid2: 4

tid3: 6

tid2: 5

tid1: 5

tid0: 5

tid4: 5

tid2: 6

tid1: 6

tid0: 6

tid4: 6

 

每个线程都按序输出1-6,执行正常。

 

ThreadLocal有各种应用场景,比如在Spring中的事务管理模块,ThreadLocal就有精彩的表现。

 

基于软件工程中的模块化设计原则,我们会将业务操作与数据访问拆分开来,将业务逻辑放在service层,将数据访问模块放在Dao层,service层通过Dao层进行数据访问,而事务管理是放在service层的,这样拆分,提高了模块的重用性,一个service有可能调用若干个dao,而要让多个dao的访问在同一个事务下,则他们必须使用同一个connection,因为性能和并发的要求,connection不会是全局变量,于是我们通过传参的方式把当前connection传到相应的调用的dao方法中。

 

 public void serviceA(){

  Connection conn = transactionManager.doBegin();

  dao1.doX(conn);

  dao2.doY(conn);

  transactionManager.doEnd(conn);

 }

 

事务管理代码依然和数据访问层紧密耦合,无法实现重用,假如现在需要的不是JDBCconnection,而是其他资源,比如是hibernatesession,那这一切是不是都要重新调整了?

 

理想中的调用应该是这样的:

 

 public void serviceA(){

  Connection  conn= transactionManager.doBegin();

  dao1.doX();

  dao2.doY();

  transactionManager.doEnd(conn);

 }

 

事务管理层和数据访问服务之间不能直接耦合,在事务开始阶段,将connection与当前线程绑定,数据访问时,从当前线程获取绑定的connection进行操作,等事务提交或回滚后,解除绑定。Spring有两个主要类实现这个功能,AbstractPlatformTransactionManagerTransactionSynchronizationManager,而核心机制就是使用了ThreadLocal

 

AbstractPlatformTransactionManager针对不同的数据访问技术,有着不同的实现类,如DataSourceTransactionManagerHibernateTransactionManager,以前者为例:

 

doBegin方法中,他将资源(connection)与当前线程绑定起来:

 


bindResource方法源码如下:


ThreadLocal与Spring 事务管理

那resources又是神马呢,聪明的你或许已经猜到了,正是我们开始讲的ThreadLocal。


ThreadLocal与Spring 事务管理

再后续的数据访问中,他就是从当前线程中获取资源(connection)进行操作的。可以看JdbcTemplate的execute()方法源码验证下:



他通过DataSourceUtils获取connection,这个connection又是怎么获得的呢?


他调用TransactionSynchronizationManager的getResource方法获取资源,判断当前线程下是否有绑定的connection,如果没有,则重新从dataSource获取。

 

Spring事务管理通过使用ThreadLocal,解除了事务管理模块与数据访问层的紧密耦合,提高了模块的可重用性,也保证了多线程环境下的对connection资源的有效管理,实现了线程安全。而要将事务管理代码从整个业务逻辑中抽离出来,提供系统性的服务,还有许多事情要做。Spring 正是通过aop机制解决这个问题的,这个我们下次再讲。


以上是关于ThreadLocal与Spring 事务管理的主要内容,如果未能解决你的问题,请参考以下文章

Spring对事务的支持

Spring事务

Java ThreadLocal 简介

Spring的数据访问---------------事务管理

spring如何保证并发的同时保证事务

分析Threadlocal内部实现原理,并解决Threadlocal的ThreadLocalMap的hash冲突与内存泄露