1 前言
ThreadLocal
是java中的一个常用类,通常用来为当前线程存储变量。
2 创建
有三种创建方式:
- 1 直接创建:
ThreadLocal theadLocal = new ThreadLocal<>();
- 2 创建时实现initialValue方法:
ThreadLocal有一个initialValue方法,默认返回null,供子类创建时实现:
所以可以在创建时实现protected T initialValue() { return null; }
initialValue
,以达到初始化数据的作用:public final static ThreadLocal<Object> theadLocal = new ThreadLocal<Object>(){ @Override protected Object initialValue() { return new Object(); } };
- 3 ThreadLocal.withInitial:
ThreadLocal的一个静态方法,通过一个
Supplier
在创建时初始化:
直接用withInitial来创建ThreadLocal:public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); }
public static final ThreadLocal<Object> current = ThreadLocal.withInitial(() -> { return new Object(); });
3 实现
3.1 ThreadLocalMap
每个Thread对象中,有一个ThreadLocal.ThreadLocalMap对象:
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal的操作(set/get/remove),就是对当前线程的ThreadLocal.ThreadLocalMap对象的操作:
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
3.2 Entry
Entry是ThreadLocalMap内部存储数据的节点:
- 1 Entry继承了WeakReference,referent为Entry的key(ThreadLocal对象);
- 2 Entry的key是ThreadLocal对象。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap中声明了Entry
数组,作为数据的存储:
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 实例化Entry数组
table = new Entry[INITIAL_CAPACITY];
// 计算数组下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
数组的下标,由ThreadLocal.threadLocalHashCode做相关位运算后确定。
ThreadLocal.threadLocalHashCode在每个对象实例化时计算:
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
4 移除
- 1 取到当前线程的ThreadLocalMap,调用ThreadLocalMap.remove:
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
- 2 根据key(ThreadLocal对象)找到对应的Entry,并执行
Reference.clear()
:
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) {
// 找到key,移除
e.clear();
expungeStaleEntry(i);
return;
}
}
}
5 关于内存泄露
- 原因:
- 虽然Entry节点继承了WeakReference,但是WeakReference在没有强引用时才会自动回收。
- WeakReference -> Entry -> TreadLocalMap -> Thread 有一条强引用路径,而一般的线程会常驻内存,不会回收,所以WeakReference也不会自动回收。
- 解决:手动
remove();