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 总结
remove时,清除的只是key(ThreadLocal对象),并没有清除value,为防止内存泄露,建议:
- 1 显式的调用remove(),并在remove前,先通过get获取value,然后对value进行清理;
- 2 声明ThreadLocal子类,并重写remove()方法:在调用super.remove()前,先对value进行统一处理。