ThreadLocal
从字面上解释为本地线程,但是使用线程局部变量称呼更为贴切其作用,这也算是名称带来的误解
线程局部变量是什么
每一个线程在栈里面分配独立的内存空间,互不影响,这是是所谓线程隔离。就每个线程而言,在执行过程中一定会涉及到在多个模块中调用不同的方法,如果需要在这些调用过程中,传递某个变量用于这个过程中使用,没有学习过ThreadLocal
的话,我们可能会依赖每个方法的参数和返回值,但是这样改造之后,方法之间就必须互相关联互相耦合,为了在线程运行中传递一个共享的值,这种改造是非常麻烦的。
所以Java
提供了ThreadLocal
类,这个类可以保持一个变量在一个线程运行过程中共享传递,而且不同线程之间互不影响,线程安全。
线程局部变量常用API
实际使用而言,这五个方法较为常用,掌握即可
ThreadLocal()
:用于构造对象,因为ThreadLcoal
作为一个类,而非接口,所以使用的时候需要通过构造方法new出来
一个空的构造方法,除了构造对象没有做其他任何事,如果需要初始化使用initialValue()
方法
initialValue()
:给ThreadLcoal
设置一个初值,每个线程第一次get的时候就可以获得这个初值了,如果需要在过程中修改初值使用setInitialValue()
方法即可,但是没什么必要,直接set(T)
更适合
默认的初始化方法返回null,而且作为一个protected
存在,是因为需要使用者重写去设置初值是什么,并返回这个初值
set(T)
:设置值,因为ThreadLcoal
带有泛型,所有可以通过泛型限定使用的值
通过当前线程去获取TheadLocalMap, 他的key就是当前ThreadLocal对象引用,value是传递过来需要设定的值,一个线程有且仅有一个ThreadLocalMap与之对应
get()
:获得通过initialValue()
设置的初值或者是set(T)
设定的值
remove()
:移除设置的值,实际上是通过移除ThreadLocalMap
中的key来移除对应的值,还记得key是什么吗?就是当前ThreadLocal
对象引用this
注意:在使用完ThreadLcoal
之后一定需要调用remove
方法移除值,否则可能会有内存泄漏问题,这个后面说
认识下ThreadLocalMap对象
在上面常用API源码认识中,多次看到这个对象,从字面上理解这是个类似于Map的结构,事实上也如此,ThreadLocalMap
是作为ThreadLocal
的一个静态内部内存在的
弱引用:当没有对象引用时,下一次GC
一定会被回收,这里将key(当前ThreadLcoal
对象引用,即this)作为弱引用,是为当ThreaedLcoal
没有用的时候,可以自动回收
这些静态内部类没有访问修饰符,表示这能在本类本包中使用
成员变量
上面说ThreadLcoalMap
很类似MAp
,实际上类似与HashMap
更为准确,看一下它的几个成员变量
构造方法
hash冲突的解决方案
firstKey.threadLocalHashCode
:这里用的是ThreadLcoal
的一个成员变量,每次调用都会触发nextHashCode()
方法进行一个特殊值累加
这里涉及到处理哈希冲突的解决方案,不同于HashMAp
的链地址法(当hash值计算相同发生冲突时,将元素依次链接形成一个链表,获取时,通过hash值找到这个链表所在的槽,再通过equals在链表上依次比较查找元素)
ThreadLocalMap
使用的方案:当发生冲突时,将冲突的值往后放到新的槽中,如果有元素则再往后移动,偏移位置的计算就是通过ThreadLcoal.threadLocalHashCode & (len - 1)
设置元素的方法
这里截的set方法源码不全,我们主要看下新元素偏移就可以了
获取元素的方法
设置set
和获取getEntry
方法都涉及到处理key=null的情况,无论是原本为null或者是GC
回收弱引用之后形成的null,这里不再讨论了
移除元素的方法
使用中的注意事项
- 我们一般使用
ThreadLocal
是将其作为private static的成员变量。其中static
是重点,静态表示内存中只有一份,因为需要在一个线程中共享变量,如果不使用静态,那么一个线程调用过程中,可能就出现创建多个ThreadLocal
的情况(所在类被创建多次),这是没有意义的。设置成static
之后表示每一个线程有且只有一个ThreadLocal
,同时也有且只有一个ThreadLocalMap
对象 - 使用完
ThreadLocal
一定要记得remove
,否则会出现内存溢出问题,原因就在于我们将其设置为static
,当ThreadLocal
不再使用的时候会被GC
回收,因为是弱引用,会形成ThreadLcoalMap
中为null的key,但是整个静态的ThreadLocal
可能因为线程还没有运行完无法销毁,出现线程脏数据导致内存溢出