ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。
具体有三个特性:
1、线程安全: 在多线程并发的场景下保证线程安全
2、传递数据: 我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
3、线程隔离: 每个线程的变量都是独立的,不会互相影响
ThreadLocal结构
每个Thread维护一个ThreadLocalMap,这个Map的key是ThreadLocal实例本身,value才是真正要存储的值Object。具体的过程是这样的:
1、每个Thread线程内部都有一个Map (ThreadLocalMap)
2、Map里面存储ThreadLocal对象(key)和线程的变量副本(value)
3、Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值
4、对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
ThreadLocal内存泄露问题
我们需要先明确内存泄漏和内存溢出的概念。Memory overflow(内存溢出),没有足够的内存提供申请者使用。Memory leak(内存泄漏)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。内存泄漏的堆积终将导致内存溢出。
每个线程创建的ThreadLocal副本作为key存入线程内部的ThreadLocalMap中,这个key是个弱引用(WeakReference),发生gc时直接就回收了。它的value值是个强引用,回收不了。此时就会出现key = null,value有值的情况,内存泄漏就发生了。
解决方案:
1、ThreadLocal使用完成之后,手动去调remove方法,删除对应的entry
2、线程销毁之后,ThreadLocal也会销毁
在ThreadLocalMap的源码中,当发生gc时,弱引用的ThreadLocal会被回收,对应的value在下一次ThreadLocalMap 调用set,get,remove中的任一方法的时候会被清除,从而避免内存泄漏
综上所述,使用弱引用的ThreadLocal可以保证key会被gc回收,value的内存泄漏问题需要我们在调用时手动remove处理
ThreadLocalMap 哈希冲突的解决办法
ThreadLocalMap是使用线性探测法来解决哈希冲突的。该方法一次探测下一个地址,直到有空的地址后插入,若整个空间都找不到空余的地址,则产生溢出。
可以把Entry[] table看成一个环形数组,假设当前table长度为16,如果计算出来key的hash值为14,如果table[14]上已经有值,并且其key与当前key不一致,那么就发生了hash冲突,这个时候将14加1得到15,取table[15]进行判断,这个时候如果还是冲突会回到0,取table[0],以此类推,直到可以插入。如果没有空余地址,就产生了溢出