尽管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