ThreadLocal 源码
ThreadLocal 提供了线程局部变量,比如我在线程A通过ThreadLocal Set一个值进去,那么在这个线程的执行过程中,我们在任何方法里都能取到这个值。
如果在这个线程中开辟的子线程里面,是取不到这个值的,ThreadLocal
只能作用于当前线程。
这就涉及到了ThreadLocal的原理,虽然我们可以在任何地点都能new一个ThreadLocal
出来,但是通过ThreadLocal
Set的变量最终是存放在当前线程的threadLocals
的Map结构中,Map的key是ThreadLocal的实例,我们可以从源码里面看到相关的处理
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 如果tab[i]不为空
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 存在引用,直接替换值
if (k == key) {
e.value = value;
return;
}
// 替换过期的k
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 为空,直接存放,增加size
tab[i] = new Entry(key, value);
int sz = ++size;
//检测是否需要调整table大小
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//重新调整大小
rehash();
}
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 从当前线程获取 ThreadLocalMap
// 一个Map结构,Key=>ThreadLocal对象,value=>通过ThreadLocal Set进去的值
ThreadLocalMap map = getMap(t);
if (map != null) {
// ***** 核心方法
// 从当前线程的ThreadLocalMap获取Entry,这里的参数this就是当前ThreadLocal对象
ThreadLocalMap.Entry e = map.getEntry(this);
// 不为空就返回
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果Map为空,这里会创建一个ThreadLocalMap
return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
// 通过threadLocalHashCode 和 表长度-1 的与运算,得出table的下标
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// e.get()说明:Entry继承了WeakReference,所以e.get() 获取的是e的引用对象,也就是key
if (e != null && e.get() == key)
return e;
else
// 如果找不到,就从e的位置继续向后找
return getEntryAfterMiss(key, i, e);
}
//查找对象
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
//获取Entry的引用对象ThreadLocal
ThreadLocal<?> k = e.get();
// 相同直接返回
if (k == key)
return e;
// 如果k为空,这是一个过时的数据,执行清理
if (k == null)
expungeStaleEntry(i);
else
// 移动下标,继续查找,一直找到链表的头,如果没有就跳出循环
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
// 清理过期数据,重新计算hash值
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 直接清理过时的数据
tab[staleSlot].value = null;
tab[staleSlot] = null;
//长度减一
size--;
// 继续查找,清理 并 重新计算hash, 直到遇到null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
// 如果为空,就清理,并将size减1
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// 不为空 ,就重新计算hash
int h = k.threadLocalHashCode & (len - 1);
// 新计算的hash于当前Hash不一致时
if (h != i) {
tab[i] = null;
// 与 Knuth 6.4 算法 R 不同,我们必须扫描直到为空,因为多个条目可能已经过时。
// 如果h位置不为空时,需要重新计算,直到h位置为空
while (tab[h] != null)
h = nextIndex(h, len);
//将i位置的e对象放入h位置
tab[h] = e;
}
}
}
return i;
}
针对threadLocalHashCode
的说明
ThreadLocals 的是实现依赖于每个线程中(Thread.threadLocals 和inheritableThreadLocals)的哈希映射。
ThreadLocal 对象充当键,通过 threadLocalHashCode 进行搜索。
这是一个自定义哈希代码(仅在 ThreadLocalMaps 中有用),它消除了在相同线程使用连续构造的 ThreadLocals 的常见情况下的冲突,同时在不太常见的情况下保持良好行为。
InheritableThreadLocal 原理
InheritableThreadLocal
继承了 ThreadLocal
重要的方法
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
ThreadLocal
中getMap
获取的是Thread.threadLocals
而InheritableThreadLocal
获取的是Thread.inheritableThreadLocals
这就是ThreadLocal
和InheritableThreadLocal
最大的不同
如何实现子线程里有父线程的对象?
主要取决与创建线程时的初始化方法
java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// 不相干的代码删掉了,不是方法的全部源码
//当前线程,也就是创建线程的线程 = 父线程
Thread parent = currentThread();
//inheritThreadLocals = true,并且父线程的inheritableThreadLocals不为空,就复制到子线程中
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
总结:
1.ThreadLocal
和InheritableThreadLocal
操作的线程变量是不一样的,所以这两个变量的值是不共享。
2.InheritableThreadLocal
通过重新getMap
的方式,将ThreadLocal
中threadLocals
替换为inheritableThreadLocals
,其他逻辑完全一样。
3.InheritableThreadLocal
在子线程创建的时候进行同步,实现代码在Thread#init方法中
4.ThreadLocal
的生命周期和Thread
一样长,如果不及时remove掉,会造成内存泄漏