看到ThreadLocal的时候多少总会跟线程安全关联在一起,因为在线程安全中涉及到共享数据,但是如果不使用共享数据如何来保证线程安全呢?网上有文章分析说,ThreadLocal的出现是为了从另外的一个角度来解决线程安全的问题,以空间换时间,每个线程拥有一份属于自己的数据副本,线程在运行过程中彼此不互相打扰,进水不犯河水。
这是对ThreadLocal的一个初步感性的认识,但是真正去理解的时候,又发现了ThreadLocalMap这个玩意,它到底和ThreadLocal是什么关系呢,一刚开始接触的时候确实会半天不知道在说什么,希望本文能够整理出一份清晰的脉络,以飨自己和其他人。
ThreadLocal的应用场景
很多时候当我们知道知识、技术或者其他等等在什么时候会用到的时候,往往会理解的更加迅速与透彻。这一小节会给出两个应用案例,一个是JDK注释文档上的官方案例,一个是借鉴的网上的资料。
在这之前先来看看,JDK源代码ThreadLocal类最开始的英文注释:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g. a user ID or Transaction ID).
翻译过来大概意思就是ThreadLocal提供了线程本地的变量(可以理解为数据),不同“线程同行”有不同的变量,那怎么拿到自己的那一份呢?就是通过通过ThreadLocal对象的get方法获取每个线程自己的数据,当然了,设置的话通过set方法。
好了,那这个ThreadLocal对象一般怎么用呢,怎么玩呢?最后一句说了,ThreadLocal实例对象一般典型的是作为一个类的私有的静态field,与线程的一些状态(这里的状态是个广义的状态,意思应该就是跟线程相关的数据)关系起来。更好的是官方JDK注释给了使用案例。
case x0
|
|
这个官方给出的案例,相当于ThreadId类包裹了ThreadLocal,给我们提供了一种方便的获取和设置线程本地数据的途径。
case x1
|
|
按照我们前面的思路来看的话,不同的线程通过getConnection()获取到的connection是不同的,各自使用各自不同的链接来操作数据库,而每个线程总是会用自己一开始获取的connection,只要这个connection不被清掉。
多个线程
我们先不管内部实现,先来测试看看,用多个线程去获取链接看是什么情况:
测试数据结果如下:
单个线程
|
|
测试数据:
通过上面的测试正面我们前面的猜想是正确的,这样也就实现了Connection对象在多个线程中的完全隔离。据说在Spring容器中管理多线程环境下的Connection对象时,采用的思路和以上代码非常相似,但是还没有进行验证。
ThreadLocal源码探析
get方法
在ThreadLocal的使用过程,用到的最多的就是get和set方法,其实就是存取每个线程自己的本地变量数据。先看get方法的源码是如何实现的:
在这里我们一开始所说的ThreadLocalMap浮出水面,这个到底是什么呢?其实很简单,说白了就是当前这个线程自己内部的一个属性变量threadLocals
,在Thread线程类中,代码如下:
拿到这个map之后(假设已经被初始化过不为null),这个时候还没结束,真正取出这个存放的值是通过this
取出来的,this
是什么?this
在前两个案例中就是这个静态的私有的nextId和connectionHolder,也就是ThreadLocal对象,通过它作为key,在每个线程自己的ThreadLocalMap中取出了线程本地的变量值。
所以思路还是很清晰的,每个线程有个map,我们在类似于connectionManager这些类中可以定义很多个ThreadLocal对象,所以根据不同的ThreadLocal对象作为key值,可以在map拿到对应的值。
到这儿其实get方法的分析可以结束了,但是其实可以继续看看getEntry的构造:
setInitialValue设置默认值
在刚才的get方法中,是假设线程所拥有的ThreadLocalMap已经被初始化,但是如果当第一次调用get方法时候,还没有初始化呢?根据上面的代码片段可以看到是调用setInitialValue()
方法,方法源代码如下:
以上两小节就是关于get方法的这个流程中主要代码实现。
set方法
set方法就更简单了,跟setInitialValue
相比就是自己手动设置线程本地变量,而不是通过默认值的方式。
关于ThreadLocal是否存在内存泄漏问题
从上面源码分析中,我们大致可以看出引用关系,当前线程>ThreadLocalMap>table>Entry>弱引用ThreadLocal和强引用value。当线程运行结束的时候,线程对象会被回收,线程内部的ThreadLocalMap也会被回收,table和Entry更不用说的,也会被回收,因此value回收。
但是也有一种情况值得考虑,就是当时线程池的时候,这个时候线程池里面的线程有的是经常活跃的,就不能这么来了,具体可以参考这篇文章,博主做了一个关于用线程池的测验,就是在最后不使用线程本地变量的时候,通过ThreadLocal的remove方法清除变量,这样也就解决了线程池可能存在的内存泄漏问题。
小结
其实问题也没我们想象的那么复杂,我们使用线程本地变量就是跟ThreadLocal打交道就行了,至于ThreadLocalMap只是每个线程Thread内部维护的一个map属性。
内部get实现的时候就是通过当前线程拿到自己的map,然后以ThreadLocal的实例为key值拿到属于自己线程的value值,就这样了。
-EOF-