前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ThreadLocal与Spring 事务管理

ThreadLocal与Spring 事务管理

作者头像
java达人
发布2018-01-31 18:39:58
1.6K0
发布2018-01-31 18:39:58
举报
文章被收录于专栏:java达人

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

代码语言:js
复制
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方法中。

代码语言:js
复制
 public void serviceA(){
  Connection conn = transactionManager.doBegin();
  dao1.doX(conn);
  dao2.doY(conn);
  transactionManager.doEnd(conn);
 }

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

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

代码语言:js
复制
 public void serviceA(){
  Connection  conn= transactionManager.doBegin();
  dao1.doX();
  dao2.doY();
  transactionManager.doEnd(conn);
 }

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

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

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

bindResource方法源码如下:

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

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

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

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2016-07-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java达人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档