ThreadLocal 的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是隔离的,在多线程环境下,可以防止自己的变量被其它线程篡改。
Spring 采用 ThreadLocal 的方式,来保证单个线程的数据库操作使用的是同一个数据库连接。
底层原理:
set 的源码:
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取 ThreadLocalMap对象
if (map != null) // 校验对象是否为空
map.set(this, value); // 不为空set
else
createMap(t, value); // 为空创建一个map对象
}
ThreadLocalMap 是从当前线程的变量 threadLocals 中获取的。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
每个线程 Thread 都维护了自己的 threadLocals 变量,所以每个线程创建 ThreadLocal 的时候,实际上数据是存在自己线程 Thread 的 threadLocals 变量里面的,别人没办法拿到,所以实现了隔离。
ThreadLocalMap 的底层结构
ThreadLocalMap 源码:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
ThreadLocalMap 的结构是数组加 Entry:
为什么用数组?
我们开发过程中一个线程可以有多个 ThreadLocal 来存放不同类型的对象,但是他们都放到 threadLocals 中,所以要用数组来存。
怎样解决 hash 冲突
ThreadLocalMap 在存储的时候会给每一个 ThreadLocal 对象一个 threadLocalHashCode。在插入过程中,根据 ThreadLocal 对象的 hash 值,定位到数组中的 i,int i = Key.threadLocalHashCode & (len - 1);
然后会判断一下:如果当前位置是空,就初始化一个 Entry 对象放在位置 i 上;如果位置 i 不为空,这个 Entry 对象的 Key 正好是即将设置的 Key,那么就刷新 Entry 中的 value;如果位置 i 不为空,Key 也不想等,那就找下一个位置,直到为空为止。
在 get 的时候,也会根据 ThreadLocal 对象的 hash 值,定位到数组中的位置,然后判断该位置 Entry 对象的 Key 是否和 get 的 Key 一致,如果不一致,就判断下一个位置,set 和 get 冲突如果严重的话,效率还是很低的。
怎样共享线程的 ThreadLocal 数据
使用 InheritableThreadLocal 可以实现多个线程访问 ThreadLocal 的值。
内存泄漏
ThreadLocal 在保存的时候会把自己当作 Key 存在 ThreadLcoalMap 中,但是 Key 被设计成 WeakReference 弱引用了。
弱引用:只具有弱引用的对象拥有更短暂的生命周期,在垃圾回收线程扫描它所管辖的内存区域的过程中,一旦发现只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
那么就会造成一个问题:ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部关联的强引用,那么在虚拟机进行垃圾回收时,这个ThreadLocal会被回收,这样,ThreadLocalMap中就会出现key为null的Entry,这些key对应的value也就再无妨访问,但是value却存在一条从Current Thread过来的强引用链。因此只有当Current Thread销毁时,value才能得到释放。如果创建ThreadLocal 的线程一直持续运行,那么就会发生内存泄漏。
解决方法:在代码的最后使用 remove 把值清空就好了。
为什么 ThreadLocalMap 的 key 要设计成弱引用
key 不设置成弱引用的话,那么 map 里面的 key 就不会被回收,也会发生内存泄漏。