说说 ThreadLocal
按照编码建议将 ThreadLocal 对象声明为 static
的,正常使用 ThreadLocal 即可.
前两天看同事的代码,同事提了一句说
ThreadLocal 的 map 的 Entry 是 WeakReference,所以会出现 ThreadLocal 对象被 GC 释放的问题,故他在代码中使用 ThreadLocal 时考虑了这个情况,不会出现问题。
闻之大惊,因为我这么多年使用 ThreadLocal 从未考虑过这种情况,细一考量会发现如果真是这样的话,ThreadLocal 的使用场景就几乎不太存在了。直觉上感觉应该是他哪里理解错了。
今天有时间,就深究了一下这个问题。
先说结论,上面的说法确实理解有误,按照编码建议将 ThreadLocal 对象声明为 static
的,正常使用 ThreadLocal 即可。
以下是探究过程。
首先写了一个例子:
public class ThreadLocalTest {
private static final ThreadLocal<Map<String, String>> holder = ThreadLocal.withInitial(HashMap::new);
private static final List<ThreadLocal<Integer>> holderList = new ArrayList<>();
public static void main(String[] args) {
init(10000, 10000);
gc();
initHolders(10000);
gc();
System.out.println(holder.get().size());
checkHolderList();
}
private static void gc() {
System.out.println("gc goes here ...");
System.gc();
}
private static void init(int size, int valueLength) {
for (int i = 0; i < size; i++) {
holder.get().put(String.valueOf(i), RandomStringUtils.random(valueLength));
}
}
private static void initHolders(int size) {
for (int i = 0; i < size; i++) {
ThreadLocal<Integer> holder = new ThreadLocal<>();
holderList.add(holder);
holder.set(i);
}
}
private static void checkHolderList() {
System.out.println(holderList.size());
for (int i = 0; i < holderList.size(); i++) {
if (holderList.get(i).get() != i) {
throw new RuntimeException("check failed");
}
}
System.out.println("check success");
}
}
//java ThreadLocalTest -verbose:gc
运行后未发现任何问题,如果将 ThreadLocal 换成 WeakReference,则测试无法运行。
那是什么原因导致的呢?看代码。
同事说的 ThreadLocal 中 Entry 为 WeakReference 的代码如下:
// TheadLocal.ThreadLocalMap.Entry
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
这里要注意,WeakReference 引用的对象是 ThreadLocal,也就是说只要你保持对 ThreadLocal 对象的引用,就不会被清除掉。而这里使用 WeakReference 的原因,也写的很清除了,主要是避免在 ThreadLocal 对象被清理的情况下发生内存泄漏。如果 ThreadLocal 对象被清理了,Thread 的成员变量 ThreadLocal.ThreadLocalMap threadLocals
会在 gc 时清理掉这些对象,避免内存泄漏。
另外,jdk 的实现中 ThreadLocal 设计为由 Thread
持有以 ThreadLocal
为 key 的 ThreadLocalMap
,而不是由 ThreadLocal
对象持有以 Thread
为 key 的 ThreadLocalMap
也是处于避免内存泄漏的考虑。因为大部分情况下 Thread 的生命周期会短于 ThreadLocal
的生命周期。