1.WeakReference的referent指向ThreadLocal的原因
ThreadLocal作为WeakReference的referent,只要ThreadLocal对象引用被置为null,Entry的key(referent)就会在下一次YGC时被回收。在使用ThreadLocal的get()和set()时,会将失效的Entry(key == null)的value置为null,使value能够被GC回收,避免内存泄漏。
2.ThreadLocal惯用法与本质
ThreadLocal instances are typically private static fields in classes
that wish to associate state with a thread
ThreadLocal对象常作为私有静态变量使用,其生命周期不会随着线程结束而结束。
- 局部变量用于方法内语句之间进行传递
- 类内域用于在类内方法之间进行传递
- TreadLocal用于在线程之间进行传递
ThreadLocal本质属性:线程间共享。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
调用该init的有8个Thread构造器:
这8个构造器的inheritThreadLocals为true。
只有下面的构造器inheritThreadLocals为false:
Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
在init中:
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
InheritableThreadLocal用来给子线程从父线程继承thread-local值。
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
InheritableThreadLocal自动与线程的inheritableThreadLocals域关联起来。
childValue()是个protected方法,其用在子线程创建时,用来从父线程copy thread-local值时,可以设置初始值。
public class RequestProcessData {
private static final InheritableThreadLocal<FullLinkContext> FULL_LINK_THREADLOCAL
= new InheritableThreadLocal<>();
public static FullLinkContext getContext() {
FullLinkContext fullLinkContext = FULL_LINK_THREADLOCAL.get();
if (fullLinkContext == null) {
FULL_LINK_THREADLOCAL.set(new FullLinkContext());
fullLinkContext = FULL_LINK_THREADLOCAL.get();
}
return fullLinkContext;
}
public static class FullLinkContext {
private String traceId;
public String getTraceId() {
if (StringUtils.isEmpty(traceId)) {
FrameWork.startTrace(null, "gujin");
traceId = FrameWork.getTraceId();
}
return traceId;
}
public void setTraceId(String traceId) {
this.traceId = traceId;
}
}
}
使用ThreadLocal和InheritableThreadLocal透传上下文时,需要注意线程间切换、异常传输时的处理,避免在传输过程中因处理不当而导致的上下文丢失。
3.ThreadLocal<T>实现本质
ThreadLocal对象本身由多线程共享。
而T类型的值各线程各不相同。
例如SimpleDateFormat是线程不安全的,可以放到ThreadLocal<DateFormat>中,然每个线程单独有一份SimpleDateFormat对象。
4.ThreadLocal使用不当引起的问题
主要问题是会产生脏数据和内存泄漏。通常是在线程池的线程中使用ThreadLocal引发的,因为线程池有线程复用和内存常驻两个特点。
4.1 脏数据
run()中没有显式地调用remove()清理与线程相关的ThreadLocal信息,下一个线程也没有set()设置初始值,则会产生脏数据。
4.2 内存泄漏
ThreadLocal一般是static对象。因此寄希望于ThreadLocal对象失去引用后,触发弱引用回收机制来回收Value是不现实的。
为了防止内存泄漏,每次用完ThreadLocal,必须要及时调用remove()方法清理。