ThreadLocal 和 InheritableThreadLocal
父子线程共享数据可以通过使用 InheritableThreadLocal
来达成目的,InheritableThreadLocal
类继承于 ThreadLocal
ThreadLocal
主要成员变量
ThreadLocal
最主要的成员变量是 ThreadLocalMap
,该对象用于存储我们需要保存的数据,ThreadLocalMap
使用 Entry
数组存储数据
Entry
继承自 WeakReference,使用 ThreadLocal
实例作为键,因此如果 Entry
的键没有被强引用持有那么当下次 GC 时就会被回收掉,该 Entry
实例的键就为 null 了,但是 value 依然存在
- 强引用 StrongReference
如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。- 软引用 SoftReference
如果一个对象只具有软引用,则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。- 弱引用 WeakReference
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。- 虚引用 PhantomReference
无法通过虚引用获取与之关联的对象实例,且当对象仅被虚引用引用时,在任何发生 GC 的时候,其均可被回收
主要方法
ThreadLocal
主要的数据结构就是 ThreadLocalMap
以及 Entry
,接着我们看一下最主要的方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
// ThreadLocalMap在当前线程被所有ThreadLocal共享
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
// 初始化map,构建table与Enrty
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal
在进行 set 时,把自己作为键。在 get 时首先获取当前 Thread
,然后通过它获取到 ThreadLocalMap
实例,最后通过 ThreadLocalMap
实例获取到 value
threadLocalHashCode 和线性探测
因为 ThreadLocalMap
使用了 Entry
数组,所以我们可以知道这块设计类似于 HashMap
的 hash 桶,在存取数据时肯定要先确定 hash 桶的位置,这就要使用 hashCode 来定位了
getEntry
是使用的 threadLocalHashCode
定位,我们来看一看这个变量
threadLocalHashCode
的生成还依赖 HASH_INCREMENT
,这是一个写死的数,十进制为 1640531527,每个 ThreadLocal
实例的 hashcode 都会根据这个值递增,并且这个递增是全局的,在整个 jvm 中不会存在重复的两个 threadLocalHashCode
值
HASH_INCREMENT
是一个很特殊的值,它能够让哈希码均匀的分布在 2 的 N 次方的数组里,也就是让 threadLocalHashCode & (table.length - 1)
的值非常均匀,也正是因为这个原因所以 ThreadLocalMap
没有采用 HashMap
的拉链法来解决 hash 冲突,而是使用线性探测
自动清除
之前在分析数据结构时发现 Entry
的 key 是一个弱引用,所以就可能会存在 key 被 GC 掉了但是 value 还存在。那么肯定有一个机制用于清除这种 Entry
实例,具体的方法就是 expungeStaleEntry
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 1.直接清除指定位置的Entry对象
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 2.遍历Entry数组直到某个Entry对象的value不为null,然后把这期间遇到的所有key为null的都删除掉
Entry e;
int i;
for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
注释中写的很清楚,主要有两个删除动作:
- 1.直接清除指定位置的 Entry 对象
- 2.遍历 Entry 数组直到某个 Entry 对象的 value 不为 null,然后把这期间遇到的所有 key 为 null 的都删除掉
主要是以下几个方法在调用 expungeStaleEntry(...)
随便举一个调用链例子:ThreadLocal 的 get 方法 -> ThreadLocalMap 的 getEntry 方法 -> getEntryAfterMiss 方法 -> expungeStaleEntry 方法
所以不难得知在调用 ThreadLocal 进行 get 或者 remove 时都会把 key 为 null 的 Entry 对象给删除掉
InheritableThreadLocal
方法
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
public InheritableThreadLocal() {
}
protected T childValue(T var1) {
return var1;
}
ThreadLocalMap getMap(Thread var1) {
return var1.inheritableThreadLocals;
}
void createMap(Thread var1, T var2) {
var1.inheritableThreadLocals = new ThreadLocalMap(this, var2);
}
}
该类很简单,就重写了 ThreadLocal
的几个方法,在原来的 ThreadLocal
中 getMap 是调用 Thread
实例获取 threadLocals
对象,现在则是获取 Thread
的 inheritableThreadLocals
对象
同理在创建时,ThreadLocal
是将实例化的 ThreadLocalMap
赋给 threadLocals
变量,InheritableThreadLocal
则是将实例化的 ThreadLocalMap
赋给 inheritableThreadLocals
变量
父子线程共享数据
如果父线程存在 inheritableThreadLocals
变量(也就是 Thread 的实例中),那么在创建子线程时就会把 父线程的 inheritableThreadLocals
引用过来,然后就可以通过这个变量进行共享数据了,调用链如下
Thread 类:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
.....
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
....
}
ThreadLocal 类:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
最后关键的地方在于 ThreadLocalMap
的初始化,如下
代码很简单直观,就是通过父线程的 ThreadLocalMap
实例拿到 Entry 数组,然后浅拷贝一份,这样子线程就拥有了父线程通过 InheritableThreadLocals
设置的数据
这个 private ThreadLocalMap(ThreadLocalMap parentMap)
方法只有在父线程使用了 InheritableThreadLocals
才会用到
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于