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);
 }

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

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

 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机制解决这个问题的,这个我们下次再讲。

原文发布于微信公众号 - java达人(drjava)

原文发表时间:2016-07-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏NetCore

Struts原理与实践

一、JDBC的工作原理 Struts在本质上是java程序,要在Struts应用程序中访问数据库,首先,必须搞清楚Java Database Connect...

22880
来自专栏LuckQI

学习Java基础知识,打通面试关~十三锁机制

14750
来自专栏Java3y

WebService就是这么简单

WebService介绍 首先我们来谈一下为什么需要学习webService这样的一个技术吧…. 问题一 如果我们的网站需要提供一个天气预报这样一个需求的话,那...

5.1K150
来自专栏程序员的碎碎念

JS动态加载以及JavaScript void(0)的爬虫解决方案

对于使用JS动态加载, 或者将下一页地址隐藏为 JavaScriptvoid(0)的网站, 如何爬取我们要的信息呢?

16820
来自专栏james大数据架构

资源等待类型sys.dm_os_wait_stats

动态管理视图  sys.dm_os_wait_stats  返回执行的线程所遇到的所有等待的相关信息。可以使用该聚合视图来诊断 SQL Server 以及特定查...

23270
来自专栏散尽浮华

Linux系统下的用户密码设定梳理

随着linux使用的普遍,对于linux用户以及系统的安全要求越来越高,而用户密码复杂程度是系统安全性高低的首要体现。因此如何对linux下用户的密码进行规则限...

33490
来自专栏*坤的Blog

公司web安全等级提升

公司的一个web数据展示系统,本来是内网的,而且是一个单独的主机,不存在远程控制的问题,所以之前并没有考虑一些安全相关的测试.但是国调安全检查的需要添加这样子的...

22540
来自专栏个人随笔

JFinal 3.3 学习 -- JFinalConfig (配置web项目)

49850
来自专栏Java3y

从零开始写项目第二篇【登陆注册、聊天、收藏夹模块】

登陆模块目标 我要将其弄成类似的登陆,功能是要比较完善的。 ? 本来我是想做一步写一步的,但是发现这样文章就会太乱,因为要改的地方太多了。前面写过的,后边就被修...

1.2K80
来自专栏一枝花算不算浪漫

[数据库连接池] Java数据库连接池--DBCP浅析.

450140

扫码关注云+社区

领取腾讯云代金券