ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。
所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
主要思路:
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();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
主要思路:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
主要思路:
虽然他叫 ThreadLocalMap,但是他没有实现 Map 接口,但是 内部也是以通过一个 Entry 数组来存储数据,这个 Entry 的 key 继承了 WeakReference,但是 value 没有继承。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
每个 ThreadLocal 对象都有一个 hash 值 threadLocalHashCode 每初始化一个 ThreadLocal 对象,hash 值就增加一个固定的 HASH_INCREMENT(0x61c88647)大小
在插入过程中,根据 ThreadLocal 对象的 hash 值,定位到 table 中的位置 i,过程如下
这样的话,在 get 时,也会根据 ThreadLocal 对象的 hash 值,定位到 table 中的位置,然后判断该位置 Entry 对象中的 key 是否和 get 的 key 一致,如果不一致,就判断下一个位置
可以发现,set 和 get 如果冲突严重的话,效率很低,因为 ThreadLoalMap 是 Thread 的一个属性,所以即使在自己的代码中控制了设置的元素个数,但还是不能控制其它代码的行为
线程池的一个线程使用完 ThreadLocal 对象之后,再也不用,由于线程池中的线程不会退出,线程池中的线程的存在,同时 ThreadLocal 变量也会存在,占用内存!造成 OOM 溢出!最根本原因是 ThreadLocalMap 中的 Entry 的 key 继承了 弱引用。
ThreadLocal.ThreadLocalMap. Map 中的 key 为一个 threadlocal 实例。这个 Map 的确使用了弱引用,不过弱引用只是针对 key。每个 key 都弱引用指向 threadlocal。 当把 threadlocal 实例置为 null 以后,没有任何强引用指向 threadlocal 实例,所以 threadlocal 将会被 gc 回收。 但是,我们的 value 却不能回收,而这块 value 永远不会被访问到了,所以存在着内存泄露。因为存在一条从 current thread 连接过来的强引用。只有当前 thread 结束以后,current thread 就不会存在栈中,强引用断开,Current Thread、Map value 将全部被 GC 回收。最好的做法是将调用 threadlocal 的 remove 方法,这也是等会后边要说的。在 ThreadLocal 的 get(),set(),remove()的时候都会清除线程 ThreadLocalMap 里所有 key 为 null 的 value。