针对ThreadLocal的源码解析,由于篇幅较长,如果写在一篇里担心会对大家的阅读造成一定的阻碍,故此将其拆分为几篇文章。具体的目录如下所示:
大家可以针对感兴趣的部分进行阅读。那么闲话少叙,我们来进入正题。
一、ThreadLocal概述
- ThreadLocal 是一个线程的本地变量,也就意味着这个变量是线程独有的,是不能与其他线程共享的。这样就可以避免资源竞争带来的多线程的问题。
- 但是,这种解决多线程安全问题的方式和加锁方式(synchronized、Lock) 是有本质的区别的,区别如下所示:
- 关于资源的管理
- 当资源是多个线程共享的,所以访问的时候可以通过加锁的方式,逐一访问资源。
- ThreadLocal是每个线程都有一个资源副本,是不需要加锁的。
- 关于实现方式
- 锁是通过时间换空间的做法。
- ThreadLocal是通过空间换时间的做法。
- 由于使用场景的不同,我们可以选择不同的技术手段,关键还是要看你的应用场景对于资管的管理是需要多线程之间共享还是单线程内部独享。
二、ThreadLocal的使用方式
ThreadLocal threadLocal = new ThreadLocal();
threadLocal.set("muse");
threadLocal.get();
是不是很简单呢?但是它其中包含的东西,还是真的不少呢。
我们要看源码,出发点自然就是set和get方法了。那么我们就看看它内部到底是什么样子的?
三、ThreadLocal源码分析
3.1> ThreadLocal、Thread、ThreadLocalMap、Entry之间的关系
3.2> ThreadLocal的set(T value)方法
【解释】红框中的代码,会是我们下面着重要介绍的。
- 当我们创建ThreadLocal后,第一次调用set方法赋值的时候,由于ThreadLocalMap还没有被创建,所以会执行createMap(t, value)方法来对ThreadLocalMap进行初始化。其中,源码和注释如下所示:
【解释】
从上面源码中我们可以看到,ThreadLocalMap是当前线程Thread的一个全局变量。从这里,我们就可以看出来,为什么说ThreadLocal是当前线程的本地变量了。
- 而在ThreadLocalMap的构造方法里,蕴含着初始化创建table数组的逻辑,源码和注释如下所示:
【解释】
从上面源码中我们可以看到,数组默认大小是16,设定的阈值为0.75的数组长度,并且根据传入的参数,创建了table数组中的第一个Entry元素对象。其中,size用来记录数组中存在的Entry元素的个数。
- 我们了解完createMap(t, value)方法之后,那么就把我们的视角切换到红框中的map.set(this, value)方法,这才是我们下面要分析的重点。由于里面代码涉及较多,所以下面我们会分为几部分来详细介绍。不过,在介绍之前,我们还是要先看一下map.set(this, value)方法的相关源码和注释:
【解释】
关于set方法其实有两个,他们之间的关系就是——通过ThreadLocal的set方法来调用ThreadLocalMap的set方法。
- 在上面源码的四个红框中,我们下面会一一进行详细介绍。
- 为了方便大家理解,我这里画了一张流程图,大家可以作为参考:
【解释】
通过上面的流程图,我们可以总结set方法有如下几个处理步骤:
- 首先,通过入参key(即:ThreadLocal对象),计算应该插入table数组的下标。
- 如果该下标所在的位置是空闲的,那么就把新插入的值封装为Entry插入进去。
- 如果该下标所在的位置已经被别的Entry占据了,那么来进行如下判断:
- 如果已存在的Entry的key值与我们的key值相同(即:是同一个ThreadLocal实例对象),那么我们只是将value值更新为方法入参的value即可。
- 如果key值不同,那么来判断,已存在的Entry是不是key==null(即:是一个“陈旧的”元素,那么我们进行替换操作)
- 如果都不满足,那就往后遍历其他的Entry元素,直到满足上述条件为止,否则会一直循环。
后面的内容,参见:ThreadLocal源码精讲(2)