关于ThreadLocal相关知识(个人理解)

**

什么是ThreadLocal?

**
在理解ThreadLocal前我们要明白什么情况下会使用ThreadLocal。
在做项目时,我们按照三层架构来开发的情况下,会有Service层调用Mapper层来完成相应的业务逻辑。拿具体案例来说,Service在做转账这个业务逻辑时,会有一个人的账户余额增加,另一个账户余额减少。但这里Service会调用Mapper中的两个方法来完成,第一个方法是修改对应数据库表中转账人的余额减少的方法,另一个方法是收账人余额增加的方法。
新建maven项目:
pom文件如下:
在这里插入图片描述
项目目录结构如下:
在这里插入图片描述
TransferStart用来模拟请求,调用Service:
在这里插入图片描述
TransferService用来调用Mapper的方法完成转账的业务逻辑:
在这里插入图片描述
AccountMapper提供关于账户的相关操作:
在这里插入图片描述

ConnectionPool封装Durid连接池,提供Connection连接:
在这里插入图片描述
运行前看一下数据库表的原始数据:
在这里插入图片描述
运行后的数据:
在这里插入图片描述

我们可以看到当用户账户增加和减少操作之间没有异常时成功了,现在我们在service执行账户增加和减少中加入一个异常再来运行。
在这里插入图片描述

数据库表的初始值:
在这里插入图片描述

运行后:

在这里插入图片描述
在这里插入图片描述

我们发现此时出现了错误,1账户减少,2账户未增加,没有保证事务的原子性。
要保证事务的原子性,我们可以在Service获取Connection对象,然后关闭自动提交,成功则提交,失败则回滚。并且Mapper方法所用的Connection要和Service获取的一致,我们不得不将Connection对象传入Mapper中去(代码省略…),这样会导致代码耦合度变高,出现了一些与业务逻辑无关的参数。
于是乎我们将Connection设置为Mapper的属性并赋值,然后在Service中去拿到那一个Connection对象,但多线程运行下会导致使用同一个Connection对象出现问题,这个时候ThreadLocal便派上用场了,它可以让每个线程运行时,有自己单独的副本,各个线程都用自己的Connection对象提交事务。此时可以对Util下的连接池更改,让同一个线程操作时获取的Connection对象是同一个对象。

ConnectionPool更改后代码如下:
在这里插入图片描述
TransferService更改后代码如下:
在这里插入图片描述
此时再运行时,同一个线程中Service和Mapper中拿到的Connection对象则会是同一个,看一下运行结果:
运行前数据库表:
在这里插入图片描述
运行后:
在这里插入图片描述
数据库表:
在这里插入图片描述
我们发现回滚成功,证明Service和Mapper中拿到的是同一个Connection对象。

那么ThreadLocal是怎么做到各个线程有自己的资源副本呢?
不看源码的话我们可能会这样理解:
在这里插入图片描述
一个threadLocal对象中有一个存放节点的数组threadLocalMap对象,而节点的Key对应着我们的线程,value就是该线程独有的数据。这样确实能实现,但这样的话threadLocal对象的节点数组可能就会很大,它会存放很多线程的数据,而且不方便管理。所以没有采用此设计方案,真正的设计方案如下:
在这里插入图片描述
不仔细看你可能觉得这是同一张图,但这儿是有很大区别的。这里的话ThreadLocalMap对象不再放在ThreadLocal对象中,而是在Thread对象中。当我们通过一个ThreadLocal对象去设值时,则会在当前Thread对象的ThreadLocalMap数组对象中去设置一个节点,该节点的key就是ThreadLocal对象,值就是我们通过ThreadLocal对象设置的值。这样就能做到每个线程对应的同一个ThreadLocal对象都有自己的值。
首先我们来看一下ThreadLocalMap这个存放数据的数组类在哪儿。
在这里插入图片描述
可以看到ThreadLocalMap是ThreadLocal类的内部静态类,但这只是ThreadLocalMap的类定义的位置,其实它实例化是创建在每个线程内部。下图是Thread类中ThreadLocalMap属性的位置。
在这里插入图片描述
回到我们刚才通过ThreadLocal对象去获取和设置每个线程自己的Connection对象的例子。
在这里插入图片描述
我们来看看ThreadLocal中的get方法会做什么:
在这里插入图片描述
首先获取当前是哪一个线程,再去通过getMap方法获取这个线程自己的一个ThreadLocalMap对象,下图是getMap方法:
在这里插入图片描述
因为是第一次获取,这个时候我们获取的map是为空的,那么他会return一个setInitialValue方法返回的值,下图是setInitialValue方法。
在这里插入图片描述
此时在setInitialValue方法中getMap返回当前对象的ThreadLocalMap对象也为空,便会走createMap为当前线程创建一个ThreadLocalMap对象,并放入值为initialValue方法返回的值,这个值是空的。
在这里插入图片描述
createMap则会为当前线程t创建一个新的ThreadLocalMap对象,第一个节点的key为this(即为我们调用此方法的threadLocal对象),value值则为initialValue方法返回的值null。
在这里插入图片描述
此时我们知道了第一次get时是空值,创建了对应线程的map和初始节点(值为空)。再回到我们例子中。
在这里插入图片描述
下一步我们判断了connection是否为空,为空则从连接池中取出连接并通过ThreadLocal对象来set。
在这里插入图片描述
set也会拿到当前线程的map调用ThreadLocalMap的set去设置,这时便会覆盖我们刚才的value为空的节点。所以在下次拿的时候,只要是同一个线程,拿到的值便会是同一个。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值