重点:
① 在ThreadLocal中存储的变量每个线程独有
② ThreadLocal中数据结构存储形式是,在每个线程中放置了一个Map存储当前线程所有的静态变量,而不是在ThreadLocal中放置了一个Map来存储所有线程的私有变量,这样有利于在线程回收之后回收ThreadLocal
③ ThreadLocalMap中解决Hash冲突的方式是逐位向后探测空位
④ 在进行操作的时候,ThreadLocal会顺便清理失效的弱引用
⑤ 在清理失效节点的时候,会尝试调整哈希值到正确的索引槽位,但是不一定会成功
⑥ 如果有在子线程中使用ThreadLocal,可以考虑使用InheritableThreadLocal
1. 底层存储数据结构
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal使用当前变量存储所有的线程静态数据,这个变量的数据结构是一个内部类,类似于一个HashMap
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
private int size = 0;
ThreadLocalMap的底层数据结构还是一个哈希桶的形式,entry节点时继承了弱引用类,如果这个节点在比较多的时间并没有被使用过,那么键会被设置为空,随后则会被清理函数移除掉。
另外一点值得注意的是,在这个map中,键值是存储的ThreadLocal变量,而不是存储的线程的变量。
按照通常的理解,我们使用一个ThreadLocal类,从中可以获取当前线程的静态变量。很简单地思路就是在ThreadLocal中持有一个HashMap,Map的键是线程,而值时线程存储的静态变量。但是其实不然,在ThreadLocal中,Map的键是当前的ThreadLocal而非Thread。
而对应的ThreadLocalMap类则是直接绑定在Thread类的属性上
大胆地猜想:
假如一个线程被回收,就会被与其相关联的所有ThreadLocal变量进行回收掉。毕竟这个变量是绑定在线程上的。
但是如果按照上述方式使用线程作为键的话,线程终止之后,如果还有地方会来引用这个键,那么其实这个Thread对象会迟迟不能够被回收。当然其ThreadLocal的静态变量也是不会被回收咯
解决hash冲突
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
在处理hash冲突的时候,会使用逐个向后查找空位的方式。这种方式比较笨拙,但是也挺简单的。考虑到一个系统可能并不会有太多的ThreadLocal变量,那其实这种方式也不会很差。
查找一个节点
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {