threadlocal存连接对象的目的_Java ThreadLocal(应用场景、用法和原理)

尽管ThreadLocal与并发问题有关,但许多程序员只将其用作“方便的参数传递”工具. Pango认为这可能不是ThreadLocal设计的目的,但它是为线程安全和一些特定的场景问题而设计的

什么是ThreadLocal!

每个ThreadLocal都可以放一个线程级变量,但它可以被多个线程共享,可以达到线程安全的目的,而且绝对是线程安全的

例如:

publicfinalstaticThreadLocal<String>RESOURCE=newThreadLocal<String>()

资源表示可以存储字符串类型的ThreadLocal对象. 此时,任何线程都可以并发地访问该变量并对其进行读写,这是线程安全的. 例如,一个线程通过资源.set(“AAAA”);将数据写入ThreadLocal,您可以使用资源.get获取值

但这并不完美. 它有很多缺陷,就像每个人都依赖它来传递参数一样. 接下来,我们将分析它的一些缺点

为什么ThreadLocal有时被用作传递参数的方便方法?例如,当许多方法相互调用时,原始设计可能不会过多考虑传递多少个变量和传递多少个参数,因此整个传递参数的过程是零散的. 进一步思考: 如果方法a调用方法B并传递8个参数,那么方法B调用方法C->方法D->方法e->方法f,只需要5个参数. 此时,API设计需要输入5个参数. 在业务开发过程中,这些方法在许多地方被重用

有一天,我们发现f方法需要添加一个参数,这个参数包含在a方法的入口参数中,此时如果我们要更改中间方法,它将涉及很多,我们不知道修改后是否会有bug. 作为程序员,我们可能认为ThreadLocal是全局的. 把它放在这里. 很容易解决

但在这个时候,你会发现这种方式在系统中有点像打补丁. 粘贴得越多,就越需要调用相关代码来使用ThreadLocal传递此参数,这可能会造成混乱. 换言之,并不是我们不允许使用它,而是我们需要确保它的进出口是可控的

关于奇怪的ThreadLocal,最难搞清楚的是“scope”. 尤其是在代码设计初期出现混乱的情况下,如果再添加许多线程局部,系统将逐渐变成一个龙看不到尽头的局面. 有了这样一件容易的事情,很多小伙伴可能会对设计更加漠不关心,因为大家都认为这些问题可以通过改变来解决. 胖哥认为这是一个恶性循环

对于这种业务场景,您应该提前准备. 您需要粗粒度的业务模型. 即使要使用ThreadLocal

可能一个类不能表达所有参数的意义,容易引起强耦合

一般来说,我们根据业务模型分解几种类型的对象作为它们的参数包装器,并根据对象属性共享的情况对它们进行抽象. 在继承关系的每一层,我们扩展相应的参数,或者向对象添加参数,共享的参数在父类中定义,因此参数逐渐标准化

让我们回到正题,讨论ThreadLocal用于什么?因此,我们将在下面讨论几个主题

为了演示ThreadLocal的应用场景,让我们看一个框架示例. Spring的事务管理器通过AOP切入业务代码. 在输入业务代码之前,它会根据相应的事务管理器提取相应的事务对象. 如果事务管理器是datasource transaction manager,它将从datasource获取一个连接对象,并在传递一些打包后将其保存在ThreadLocal中. Spring还包装数据源,重写getconnection()方法,否则方法的返回将由Spring控制,这样Spring就可以使程中多次获得的连接对象相同

为什么把它放在ThreadLocal?由于spring不能在AOP之后将参数传递给应用程序,所以应用程序的每个业务代码都是预先定义的java threadlocal实例,spring不要求连接入口参数必须写入业务代码的入口参数中. 此时,spring选择ThreadLocal以确保连接对象始终程内,并且可以随时获得. 此时,spring非常清楚何时回收连接,即何时从ThreadLocal中删除元素(详见第9.2节)

从spring事务管理器的设计可以看出,spring通过使用ThreadLocal得到了一个完美的设计思想. 同时,很清楚什么时候应该在设计中删除ThreadLocal中的元素. 因此,我们只是认为ThreadLocal应该用于全局设计,而不是间接的修补方法

在理解了基本的应用程序场景之后,让我们看一个示例. 定义一个类来存储静态ThreadLocal对象. 通过多个线程并行设置并获取ThreadLocal对象,并打印该值以查看每个线程设置的值是否与检索到的值相同. 代码如下:

清单5-8简单的ThreadLocal示例

publicclasthreadlocaltest{staticclassResourceClass{publicfinalstaticThreadLocal<String>RESOURCE\1=newThreadLocal<String>);publicfinalstaticThreadLocal<String>RESOURCE\2=newThreadLocal<String>;}staticclassA{publicvoisetone(Stringvalue){ResourceClass.RESOURCE\1.设定(值);}publicvoidset2(字符串值){ResourceClass.RESOURCE\2.设置(值);}}静态类b{publicvoiddisplay(){系统输出打印( ResourceClass.RESOURCE\1.get()+“: ”+ResourceClass.RESOURCE\本文分析了{ResourceClass.RESOURCE\1.删除();ResourceClass.RESOURCE\2.删除();}}}.start();}}

我们先来谈谈这个代码

定义两个ThreadLocal变量的最终目的是查看最后两个值是否可以对应,以便有机会证明ThreadLocal保存的数据可能是线程专用的

两个内部类仅用于使测试简单且易于直观地理解. 您还可以将此示例的代码拆分为多个类,并得到相同的结果

测试代码传递参数更方便,因为传递参数很方便,但它只用于测试

在finally中有一个remove()操作,用于清除数据. 为什么要清除数据将在稍后详细解释

试验结果如下:

线程-6:值=(6)

线程-9:值=(9)

线程-0:值=(0)

线程-10:值=(10)

线程-12:值=(12)

线程-14:值=(14)

线程-11:值=(11)

线程3:值=(3)

线程-5:值=(5)

线程-13:值=(13)

线程2:值=(2)

线程-4:值=(4)

线程-8:值=(8)

线程7:值=(7)

线程1:值=(1)

您可以看到输出线程顺序不是最初定义线程的顺序. 理论上可以解释为多线程应该并发执行,但每个线程中的值仍然可以保持对应,说明这些值已经达到了线程私有化的目的

共享变量不能是线程私有的,这不是真的吗?它如何使线程私有化?这就要求我们对这个原则有一点了解,否则用起来就不那么放心了. 请看下面的介绍

从前面的操作中,我们可以看到ThreadLocal最常见的操作是set、get和remove. 让我们看看这三个动作都做了些什么. 首先,看看set操作. 源代码片段如图5-5所示

图5-5螺纹铝套源代码段

中的第一个代码

图5-5取出当前线程T,然后在它进入当前线程时调用getMap(T)方法. 换句话说,此方法返回的ThreadLocalMap与当前线程相关. 进一步确定如果映射不为空,则设置到映射的键是this,值是外部传入的参数. 这是什么?是定义的ThreadLocal对象

代码中有两条路径要跟踪,getmap(thread)和createmap(thread,t). 首先,查看getmap(T)操作,如图5-6所示

图5-6 getmap(线程)操作

在这里,我们可以看到threadlocalmap实际上是线程中的一个属性. 它在thread类中的定义是:

ThreadLocal.ThreadLocalMapthreadLocals=null

这个方法很容易混淆,因为这个threadlocalmap是ThreadLocal中的一个内部类,它作为一个属性存在于thread类中. ThreadLocal本身成为存储在此映射中的键,用户输入的值是value. 太乱了,说不清楚. 画一张图看看(见图5-7)

简而言之,这个映射对象作为一个私有变量存在于线程中,所以它是线程安全的. 螺纹局部焊道线程.当前线程()获取当前线程并获取映射对象. 同时,将map对象作为密钥进行读写. 因为你使用了map对象

本文来自电脑杂谈,转载请注明本文网址:

http://www.pc-fly.com/a/jisuanjixue/article-228885-1.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值